ores.cpp.history-provider-registrar.history_provider_registrar_implementation
Table of Contents
Per-entity history-provider registrar implementation. Defines
register_<entity>_history_provider() which registers the entity's
history provider under the product.component.entity_singular key.
The provider body constructs the entity's service from the scoped
context, reads the entity's version history through the service, and
renders each version through the presentation mapper, via
ores::history::service::build_entity_history_versions(). An entity whose
rows the system tenant holds (system_tenant_validation), such as the
pricing engine vocabulary, is read in the system tenant: the caller's
tenant holds none of those rows, so a read there answers an empty history
for a row the caller may read.
The provider is registered with the permission a caller needs to read
the history: the entity's own read code, <component>::<entity_plural>:read.
A history holds every version of the rows the entity's reads serve, so it
needs the same code, and the dispatcher refuses a caller who does not hold
it. The history subject is never on the allow-list of
Authorised reads, so a history
is guarded even when some of the entity's reads are open.
Each entity's registrar includes only that entity's service and mapper headers, so a compile failure in one entity's provider cannot block the rest of the service.
A compound-key entity emits a registrar with an empty body. Its rows
have no single-string id on the history_provider interface, so
nothing can be registered until a composite-key representation
exists. The direct service call carries the full key.
An entity whose declared key is shared by the rows of other scopes, such as a
contact type held by every party, is read by the row's own id
(history_by_row_id). The entity id is then the row's id, not the declared
key, so the history of one party's contact is never another party's.
Model inputs consumed by this template:
entity_singular(Flags) — the service class, mapper function, and file names.entity_singular_short(C++** Repositorydrawer) — the service method'sget_<short>_historyspelling, so irregular entities come out right (get_type_history,get_centre_history).entity_plural(Flags) — the read code's resource name.productandcomponent(Flags) — theentity_typekey prefix, composed identically to the notify_trigger SQL facet'sentity_name.
The product, component, and entity_singular composition mirrors
the SQL create trigger verbatim, so the server-side provider key and
the row-change side of the history pipeline can never drift out of
sync.
See the Template variable reference for the complete list of available variables and their semantics.
1. Template
The full template source. Edit here and re-tangle with
compass build --direct tangle_codegen_templates to regenerate
library/templates/cpp_history_provider_registrar.cpp.mustache.
{{! GENERATED FILE — tangled from projects/ores.codegen/library/templates/ores.cpp.history-provider-registrar.history_provider_registrar_implementation.org. Edit the org source. }}
{{{cpp_license}}}
{{#domain_entity}}
#include "ores.{{component_core}}/messaging/{{entity_singular}}_history_provider_registrar.hpp"
#include "ores.history.core/service/dispatch_registry.hpp"
{{^primary_key.is_compound}}
#include "ores.database/domain/context.hpp"
#include "ores.{{component_core}}/presentation/{{entity_singular}}_history_field_mapper.hpp"
#include "ores.{{component_core}}/service/{{entity_singular}}_service.hpp"
#include "ores.history.api/service/version_builder.hpp"
{{#system_tenant_validation}}
#include "ores.utility/uuid/tenant_id.hpp"
{{/system_tenant_validation}}
{{#history_by_row_id}}
#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid_io.hpp>
{{/history_by_row_id}}
{{/primary_key.is_compound}}
namespace ores::{{component}}::messaging {
void register_{{entity_singular}}_history_provider(
ores::history::service::dispatch_registry& registry) {
{{#primary_key.is_compound}}
// A compound-key row has no single-string id on the history_provider
// interface, so nothing is registered for it yet. The direct service
// call carries the full key; a provider shape is a composite-key
// representation decision that stays open until a consumer exists.
(void)registry;
{{/primary_key.is_compound}}
{{^primary_key.is_compound}}
registry.register_history_provider(
"{{product}}.{{component}}.{{entity_singular}}",
"{{component}}::{{entity_plural}}:read",
[](const ores::database::context& scoped_ctx, const std::string& entity_id) {
{{#system_tenant_validation}}
// The rows are the registry's, held by the system tenant, so the
// read is scoped there. A read in the caller's tenant answers an
// empty history for a row the caller may read.
const auto sys_ctx = scoped_ctx.with_tenant(
ores::utility::uuid::tenant_id::system(), scoped_ctx.actor());
service::{{entity_singular}}_service svc(sys_ctx);
{{/system_tenant_validation}}
{{^system_tenant_validation}}
service::{{entity_singular}}_service svc(scoped_ctx);
{{/system_tenant_validation}}
{{#history_by_row_id}}
// The declared key is shared by the rows of other scopes, so the
// history is addressed by the row's own id.
auto versions = svc.get_{{entity_singular_short}}_history(
boost::lexical_cast<boost::uuids::uuid>(entity_id));
{{/history_by_row_id}}
{{^history_by_row_id}}
auto versions = svc.get_{{entity_singular_short}}_history(entity_id);
{{/history_by_row_id}}
return ores::history::service::build_entity_history_versions(
versions, presentation::render_{{entity_singular}}_fields);
});
{{/primary_key.is_compound}}
}
}
{{/domain_entity}}
{{#junction}}
{{! The provider reads through the repository: a junction's service carries the
protocol's operations and nothing a history needs, and one that an operation
model owns has no service at all. A junction's history is read by the parent side: the id is the parent's,
and the answer is every version of every link of that parent, closed links
included. A link has two keys, so no single link id would do. The rows
arrive grouped by link and newest first, and each group is numbered and
diffed on its own, because a version number is a link's, not the parent's. }}
#include "ores.{{component_core}}/messaging/{{name_singular}}_history_provider_registrar.hpp"
#include "ores.database/domain/context.hpp"
#include "ores.history.api/messaging/history_protocol.hpp"
#include "ores.history.api/service/version_builder.hpp"
#include "ores.history.core/service/dispatch_registry.hpp"
#include "ores.{{component_core}}/presentation/{{name_singular}}_history_field_mapper.hpp"
#include "ores.{{component_core}}/repository/{{name_singular}}_repository.hpp"
#include <algorithm>
#include <iterator>
#include <string>
#include <vector>
{{#history_parent.is_uuid}}
#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
{{/history_parent.is_uuid}}
namespace ores::{{component}}::messaging {
void register_{{name_singular}}_history_provider(
ores::history::service::dispatch_registry& registry) {
registry.register_history_provider(
"{{history_entity_type}}",
"{{history_read_permission}}",
[](const ores::database::context& scoped_ctx, const std::string& entity_id) {
repository::{{name_singular}}_repository repo(scoped_ctx);
{{#history_parent.is_uuid}}
const auto rows = repo.read_all_by_{{history_parent.column_short}}(
boost::lexical_cast<boost::uuids::uuid>(entity_id));
{{/history_parent.is_uuid}}
{{^history_parent.is_uuid}}
const auto rows = repo.read_all_by_{{history_parent.column_short}}(entity_id);
{{/history_parent.is_uuid}}
std::vector<ores::history::messaging::entity_history_version> versions;
for (auto first = rows.begin(); first != rows.end();) {
const auto last = std::find_if(first, rows.end(), [&](const auto& row) {
return row.link.{{history_child.column}} != first->link.{{history_child.column}};
});
const std::vector<repository::{{name_singular}}_history_row> link(first, last);
auto own = ores::history::service::build_entity_history_versions(
link, presentation::render_{{name_singular}}_fields);
versions.insert(versions.end(), std::make_move_iterator(own.begin()),
std::make_move_iterator(own.end()));
first = last;
}
return versions;
});
}
}
{{/junction}}
2. See also
- Parent facet: ores.cpp.history-provider-registrar
- Template variable reference
- ores.cpp.history-provider-registrar.history_provider_registrar_header — the paired header.
- ores.cpp.presentation — the mapper this implementation renders through.