TemporalFieldTraits<T>: replace hard-coded entity_id/valid_from/valid_until with a trait #10
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Extend
TemporalRepository<T>(and any other decorator that touches canonical temporal fields) to access fields via aTemporalFieldTraits<TDto>specialisation instead of hard-coded member names likedto->valid_from,dto->valid_until,dto->entity_id.Motivation and design sketch are in the first comment.
Acceptance
include/oatpp-authkit/repo/TemporalFieldTraits.hppdefines an undefined primary templateTemporalFieldTraits<TDto>exposing static accessorsentityId(d),validFrom(d),validUntil(d)returningoatpp::String&.OATPP_AUTHKIT_REGISTER_TEMPORAL(PersonDto, entity_id, valid_from, valid_until)) generates the specialisation in one line.TemporalRepository<T>rewrites every direct field access through the trait.static_assertverifies the specialisation exists in the constructor.ITemporalEntitybecomes redundant — either remove it or downgrade it to a tag with no required fields. (Decision deferred to implementation time.)valid_from/valid_untiland exercises the full save → close → history path against it.Out of scope
Notes
Not urgent — Person pilot in fewo-webapp#457 ships fine without this. Tackle when there's a free slot or when a second temporal DTO arrives with column names that don't match the canonical three.
C++ traits in one paragraph
A trait is a class template you specialise per type to give that type extra compile-time information or behaviour without touching the type itself. The primary template is usually undefined or empty; users provide a
template<> struct Trait<MyType> { ... }specialisation that exposes static members (types, constants, or functions). The library code then writesTrait<T>::somethinginstead of hard-coding assumptions aboutT. It is the C++17/20 way to do "structural typing" — like Rust traits or Go interfaces, but resolved at compile time with zero runtime cost.Where #7/#8 hard-codes things today
TemporalRepository<T>is generic onTDtobut actually only works with DTOs that have three specific oatppStringfields namedentity_id,valid_from,valid_until. The header even says so:So
live->valid_until = oatpp::String(nowIso)works only because every consumer DTO happens to use that exact name. Rename the column in one DTO and it silently fails to compile (or worse, the wrong field gets touched if names overlap).What a trait-based extension looks like
Add a
TemporalFieldTraits<T>next toITemporalEntity:Then
TemporalRepository<T>stops touching->valid_untildirectly:A
static_assert(std::is_same_v<decltype(F::validFrom(d)), oatpp::String&>)in the constructor turns "field shape mismatch" from a runtime mystery into a compile error pointing at the DTO.Why it is worth doing
valid_from_tsoreffective_fromcan opt in without renaming columns.ITemporalEntitymarker becomes redundant (the trait is the marker).Agent Evaluation
Feasibility: High. Single header file plus a one-line registration macro; rewrite a handful of
dto->valid_*accesses inTemporalRepository.hppto go throughTemporalFieldTraits<T>::*. No external dependencies, no API break beyond requiring the macro at consumer sites.Impact: Modest now (one consumer — the upcoming Person pilot — happens to use the canonical names anyway), real later. Removes the documentation-enforced field-shape constraint and turns shape mismatches into pinpoint compile errors. Also retires
ITemporalEntityas a marker, since the trait specialisation is the marker.Effort: Small. Estimate ~150 LOC across
TemporalFieldTraits.hpp+ edits toTemporalRepository.hpp+ one new test that exercises a DTO with non-canonical column names.Recommendation: Accept. The system is pre-production, no migration cost; better to land the trait surface before more consumers bake in the assumption.
Implementation plan
include/oatpp-authkit/repo/TemporalFieldTraits.hpp: undefined primaryTemporalFieldTraits<TDto>, registration macroOATPP_AUTHKIT_REGISTER_TEMPORAL(Dto, IdMember, FromMember, UntilMember)expanding to a specialisation with three staticoatpp::String&accessors.TemporalRepository.hpp: addusing F = TemporalFieldTraits<TDto>;, replace everydto->entity_id/->valid_from/->valid_untilaccess withF::entityId(*dto)etc. Addstatic_assert(std::is_same_v<decltype(F::validFrom(*dto)), oatpp::String&>)in the constructor.ITemporalEntityfate — recommend deleting it; the trait specialisation now carries the contract. Remove thestatic_assert(std::is_base_of_v<ITemporalEntity, TDto>).test_temporal_field_traits.cpp: a DTO witheffective_from/effective_until(non-canonical names), full save → close → history flow.GIT_TAGand add the macro to their temporal DTOs.Evaluated #10 (Small) — recommend Accept.
Implemented #10 in
1baff07— 8/8 tests pass, including new test_temporal_field_traits exercising a DTO with id/effective_from/effective_until column names. Auto-closed viaCloses #10. Library bumped 0.4.0 → 0.5.0.Implemented #10 → commit
1baff07. Newrepo/TemporalFieldTraits.hppwith undefined primary template +OATPP_AUTHKIT_REGISTER_TEMPORALmacro;TemporalRepository<T>rewritten to access fields through the trait;ITemporalEntityremoved; existing decorator/interface tests migrated; newtest_temporal_field_traits.cppexercises a DTO withid/effective_from/effective_untilcolumns end-to-end. Version bumped to 0.5.0; all 8 ctest cases pass.