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
- 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.
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.risk,ores.accounts). - Fields: list of fields with their types and descriptions.
Step 1: Create the domain class definition
Create the header file under projects/COMPONENT/include/COMPONENT/domain/TYPE_NAME.hpp.
The domain class should:
- Use a
structwith all fields public. - Include comprehensive doxygen comments for the struct and all fields.
- Use appropriate C++ types:
boost::uuids::uuidfor unique identifiersstd::stringfor text fieldsboolfor flagsintor appropriate numeric types for countersstd::chrono::system_clock::time_pointfor timestampsboost::asio::ip::addressfor IP addresses
- Follow the pattern established in
projects/ores.accounts/include/ores.accounts/domain/account.hpp.
Example structure:
namespace ores::COMPONENT::domain { /** * @brief Brief description of the domain type. */ struct TYPE_NAME final { /** * @brief Description of field. */ TYPE field_name; // ... more fields }; }
Step 2: Create JSON I/O support
Create two files for JSON serialization:
- Header:
projects/COMPONENT/include/COMPONENT/domain/TYPE_NAME_json_io.hpp - Implementation:
projects/COMPONENT/src/domain/TYPE_NAME_json_io.cpp
The header should declare:
#include <iosfwd> #include "COMPONENT/domain/TYPE_NAME.hpp" namespace ores::COMPONENT::domain { /** * @brief Dumps the TYPE_NAME object to a stream in JSON format. */ std::ostream& operator<<(std::ostream& s, const TYPE_NAME& v); }
The implementation should:
- Include
<rfl.hpp>and<rfl/json.hpp> - Include
ores.utility/rfl/reflectors.hppfor custom type support - Use
rfl::json::write()for serialization - Follow the pattern in
projects/ores.accounts/src/domain/account_json_io.cpp
Step 3: Create table I/O support
Create two files for table output:
- Header:
projects/COMPONENT/include/COMPONENT/domain/TYPE_NAME_table_io.hpp - Implementation:
projects/COMPONENT/src/domain/TYPE_NAME_table_io.cpp
The header should declare:
#include <iosfwd> #include <vector> #include "COMPONENT/domain/TYPE_NAME.hpp" namespace ores::COMPONENT::domain { /** * @brief Dumps the TYPE_NAME object to a stream in table format. */ std::ostream& operator<<(std::ostream& s, const std::vector<TYPE_NAME>& v); }
The implementation should:
- Include
<fort.hpp>for table formatting - Include
<boost/uuid/uuid_io.hpp>for UUID string conversion - Use
fort::char_tablewithFT_BASIC_STYLE - Create appropriate column headers using
fort::header - Format special types appropriately:
- UUIDs: use
boost::uuids::to_string() - Booleans: convert to "Y"/"N"
- Timestamps: format using
std::put_time()as "YYYY-MM-DD HH:MM:SS" - IP addresses: use
.to_string()
- UUIDs: use
- End each row with
fort::endr - Follow the pattern in
projects/ores.accounts/src/domain/account_table_io.cpp
Step 4: Create generator support
Create two files for test data generation:
- Header:
projects/COMPONENT/include/COMPONENT/generators/TYPE_NAME_generator.hpp - Implementation:
projects/COMPONENT/src/generators/TYPE_NAME_generator.cpp
The generator class should:
- Provide a static method
generate()that creates a random instance - Provide a static method
generate_set(size_t n)that creates n random instances - Use
faker-cxxlibrary for generating realistic fake data - Use
ores.utility/uuid/uuid_v7_generatorfor generating UUIDs - Follow the pattern in
projects/ores.accounts/src/generators/account_generator.cpp
Step 5: Create repository entity and mapper
Create the database entity and mapper for ORM support:
- Entity header:
projects/COMPONENT/include/COMPONENT/repository/TYPE_NAME_entity.hpp - Entity implementation:
projects/COMPONENT/src/repository/TYPE_NAME_entity.cpp - Mapper header:
projects/COMPONENT/include/COMPONENT/repository/TYPE_NAME_mapper.hpp - Mapper implementation:
projects/COMPONENT/src/repository/TYPE_NAME_mapper.cpp
One domain class does not always map to a single entity. Entities reflect database tables. Clarify with the user how domain classes should be mapped to database entities.
The entity should:
- Use sqlgen annotations for table and column mapping
- Include all fields from the domain type.
- Follow the pattern in
projects/ores.accounts/include/ores.accounts/repository/account_entity.hpp
The mapper should:
- Provide
to_domain()method to convert entity to domain type - Provide
from_domain()method to convert domain type to entity - Handle type conversions (e.g., UUID to string, timestamp conversions)
- Follow the pattern in
projects/ores.accounts/src/repository/account_mapper.cpp
Step 6: Create repository with CRUD operations
Create the repository class:
- Header:
projects/COMPONENT/include/COMPONENT/repository/TYPE_NAME_repository.hpp - Implementation:
projects/COMPONENT/src/repository/TYPE_NAME_repository.cpp
The repository should provide:
- Constructor accepting a
contextparameter sql()method returning the table creation SQL- Write methods:
void write(const domain::TYPE_NAME& obj)void write(const std::vector<domain::TYPE_NAME>& objs)
- Read methods:
std::vector<domain::TYPE_NAME> read_latest()std::vector<domain::TYPE_NAME> read_latest(const KEY_TYPE& key)std::vector<domain::TYPE_NAME> read_all()std::vector<domain::TYPE_NAME> read_all(const KEY_TYPE& key)std::vector<domain::TYPE_NAME> read_at_timepoint(const std::string& timestamp)std::vector<domain::TYPE_NAME> read_at_timepoint(const std::string& timestamp, const KEY_TYPE& key)
Implementation details:
- Use sqlgen for database operations
- Use the mapper to convert between domain and entity types
- Include proper error handling with
ensure_success() - Include logging using
BOOST_LOG_SEV - Follow the pattern in
projects/ores.accounts/src/repository/account_repository.cpp
Step 7: Update CMakeLists.txt
The CMakeLists.txt in projects/COMPONENT/src/CMakeLists.txt will use
GLOB_RECURSE to pick up all *.cpp files automatically so do not add new
files.
Ensure the following dependencies are linked:
libfort::fortfor table I/Oreflectcpp::reflectcppfor JSON I/Ofaker-cxx::faker-cxxfor generatorssqlgen::sqlgenfor repositoryBoost::boostfor various Boost libraries
Step 8: Create tests
Create comprehensive tests for the new domain type under projects/COMPONENT.tests/:
- Domain tests: test JSON and table serialization
- Generator tests: verify generator produces valid instances
- Repository tests: test all CRUD operations
Follow the test patterns in projects/ores.accounts.tests/.
Step 9: Build and verify
- Reconfigure CMake to pick up new files:
cmake --preset linux-clang-debug
- Build the project:
cmake --build --preset linux-clang-debug --target COMPONENT.lib
- Build and run tests:
cmake --build --preset linux-clang-debug --target COMPONENT.tests ./build/output/linux-clang-debug/publish/bin/COMPONENT.tests
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