Skip to main content

Configure a Custom ID Generator

Evidence status: Java 1.525-RELEASE interfaces and runtime wiring were verified from released JAR signatures/bytecode. Rust 4.1.1 interfaces were verified from the published crate source, and the generated SQLite Golden Path registered its provider ID-space generator. Custom distributed generators and Java persistence remain unexecuted in this documentation baseline.

Problem

Replace the default persistence-ID strategy or add a business-facing number without editing generated entities or assigning IDs inconsistently in service code.

Choose the Correct ID Boundary

NeedJava boundaryRust boundary
Persistence primary key and entity referencesInternalIdGenerationServiceInternalIdGenerator
Human/business-facing number such as an order numberBusinessIdGenerator capabilityNo equivalent current contract verified here

Do not use a business number as a persistence key merely because both are called “ID.” Define uniqueness, immutability, exposure, retry, and migration rules separately.

All persistence IDs produced by a custom generator must follow the Portable Entity ID Range: persisted values are 1..=2^63-1, zero is reserved, and JSON transports IDs as decimal strings.

Java Internal ID Path

The released Java interface is exact:

public interface InternalIdGenerationService {
Long generateId(UserContext ctx, Entity entity);
default long nextId(String typeName) { /* unsupported by default */ }
}

Register the implementation while assembling TeaQLRuntime:

TeaQLRuntime runtime = TeaQLRuntime.builder()
.metadata(metaFactory)
.idGenerationService(internalIdGenerationService)
.build();

When saveGraph(...) sees an entity whose ID is null, runtime 1.525-RELEASE calls generateId(ctx, entity) and assigns the result through its internal entity contract before mutation execution. Application code should not call internal setters.

Implement nextId(String) as well only when framework components without an entity instance need type-name allocation. Its default implementation throws UnsupportedOperationException.

Rust Internal ID Path

Rust 4.1.1 defines:

pub trait InternalIdGenerator: Send + Sync {
fn generate_id(&self, entity: &str) -> Result<u64, RuntimeError>;
}

Register a generator during context assembly:

use teaql_runtime::{SnowflakeIdGenerator, UserContext};

let ctx = UserContext::new()
.with_module(my_domain::module())
.with_internal_id_generator(SnowflakeIdGenerator::new(3, 1));

The two constructor values are worker and datacenter IDs. They must be unique within the deployment topology. The built-in implementation accepts values from 0 through 31 for each and handles sequence rollover and clock regression according to its source implementation.

For a provider-backed sequence, use the generator emitted or supplied by that provider. The verified SQLite-generated runtime creates SqliteIdSpaceGenerator::from_executor(...) and registers it with set_internal_id_generator(...). Keep provider construction in runtime assembly rather than in business services.

Java Business ID Path

Java business IDs use a separate exact interface:

public interface BusinessIdGenerator {
String generateBusinessId(
UserContext ctx,
Entity entity,
EntityDescriptor entityDesc,
PropertyDescriptor propertyDesc);
}

UserContext.generateBusinessId(...) resolves this interface as a capability. DefaultUserContext stores capabilities by class name, so the source-verified registration shape is:

ctx.put(BusinessIdGenerator.class.getName(), businessIdGenerator);

Runtime 1.525-RELEASE does not automatically call the business generator from saveGraph(...). The handwritten creation policy must resolve the generated entity/property descriptors, call generateBusinessId(...), and assign the result through the exact generated update method. Inspect that method and the generated metadata rather than guessing either one.

The released InMemoryBusinessIdGenerator reads a metadata entry named business_id_rule; its PREFIX, LENGTH format is implementation-specific and its sequence state is process-local. Do not use it as a cluster-wide guarantee.

Verification

Before production adoption, test:

  1. IDs are unique under the expected thread/process/node concurrency.
  2. New entities receive IDs before provider mutation; updates retain IDs.
  3. Graph saves allocate every missing ID without collisions.
  4. Retries and transaction rollback have an explicitly accepted gap policy.
  5. Restart, failover, clock rollback, and worker-ID duplication are covered.
  6. Provider sequence tables are created/migrated under controlled permissions.
  7. Business IDs remain immutable and unique in their documented tenant/global scope.
  8. Logs and public APIs expose only the intended identifier.

Record implementation, runtime/provider/database versions, topology, test command, collision count, and restart/failover results.

Failure Modes

SymptomMost likely causeSmallest credible fix
Java entity reaches insert with no IDNo internal service was registered or generator returned null.Verify runtime assembly and fail fast on invalid generator output.
nextId throwsCustom Java service implements only generateId.Implement nextId if the selected framework component requires it.
Duplicate Rust IDsWorker/datacenter assignment or custom generator is not globally unique.Centralize topology allocation and run multi-node collision tests.
Business ID capability is missingBusinessIdGenerator was not placed in the context.Register it by capability class name during context creation.
Business number changes on updateCreation policy regenerates an existing value.Generate only when the exact generated field is empty and test retries.
Generated setter is missingHandwritten code guessed a method.Inspect the generated entity and use its emitted update method.

References