Command Model
Command hierarchy
The Command interface is a Java sealed interface with three permitted subtypes:
public sealed interface Command
permits TenantCommand, UserCommand, GlobalCommand
Every command carries two metadata fields:
-
getCmdUuid()— unique identifier, generated automatically by the tracing interceptor -
getCmdSourceRef()— optional reference to the source that triggered the command. Often set to thecmdUuidof another command, creating a chain that lets you trace how one command propagates changes through the system.
TenantCommand
For operations scoped to a tenant.
Extends Command and TenantIdAware.
@Data
@Introspected
public class CreateOrderCommand implements TenantCommand {
private UUID cmdUuid;
private String cmdSourceRef;
private String tenantId;
// command-specific fields
}
UserCommand
For operations scoped to a user (without tenant context).
Extends Command and UserIdAware.
Combining interfaces
The sealed subtypes define the primary scope of a command, but marker interfaces can be freely combined. This is composition over inheritance — pick a base type and mix in what you need:
@Data
@Introspected
public class GenerateTokenCommand implements TenantCommand, UserIdAware {
private UUID cmdUuid;
private String cmdSourceRef;
private String tenantId;
private String userId;
// tenant-scoped, but also tracks the user
}
@Data
@Introspected
public class CreateReportCommand implements TenantCommand, DryRunEnabled, AttributesEnabled {
private UUID cmdUuid;
private String cmdSourceRef;
private String tenantId;
private boolean dryRun;
// tenant-scoped, supports dry-run, carries custom attributes
@Override
public Map<String, Object> attributes() {
return Map.of("reportType", "annual", "year", 2025);
}
}
The tracing framework inspects these interfaces at runtime:
-
TenantIdAware→ populatestenant_idin the command log -
UserIdAware→ populatesuser_id -
DryRunEnabled→ sets thedry_runflag -
AttributesEnabled→ stores the attributes map as JSONB
Marker interfaces
Commands can implement additional interfaces to enable framework behaviors.
DryRunEnabled
Commands that support a dry-run mode.
When dryRun() returns true, the tracing framework records the command with the dry_run flag set.
public class CreateReportCommand implements TenantCommand, DryRunEnabled {
// ...
private boolean dryRun;
@Override
public boolean dryRun() {
return dryRun;
}
}
AttributesEnabled
Commands that carry custom key-value attributes.
The attributes() map is stored in the attributes JSONB column of the command log.
public class ProcessPaymentCommand implements TenantCommand, AttributesEnabled {
// ...
@Override
public Map<String, Object> attributes() {
return Map.of("paymentMethod", "invoice", "currency", "NOK");
}
}
ClientRefAware
For commands that carry a client-supplied reference identifying the originating client of the command chain (for example a machine, browser-id, app, or panel-id).
Unlike getCmdSourceRef(), which is replaced when a command spawns a new command, clientRef propagates unchanged through the chain so that events triggered by the originator can be recognised by the same client.
public class CreateOrderCommand implements TenantCommand, ClientRefAware {
private UUID cmdUuid;
private String cmdSourceRef;
private String tenantId;
private String clientRef;
// ...
}
getClientRef() may return null when the client did not supply a reference.
The tracing framework propagates a non-null clientRef from the root command to spawned commands automatically.
Redactable
For commands carrying a payload that must not land in the audit log row — typically file uploads, raw imports, or fields covered by data-handling rules.
The framework calls redactedForTracing() just before cmd_body is built and uses what it returns as the body source.
Identity (cmd uuid, tenant/user/client refs) is preserved automatically — Redactable only governs the body.
public record UploadInvoicePdfCommand(
UUID cmdUuid,
String cmdSourceRef,
String tenantId,
byte[] pdfBytes
) implements TenantCommand, Redactable {
@Override
public Optional<Command> redactedForTracing() {
return Optional.of(new UploadInvoicePdfCommand(cmdUuid, cmdSourceRef, tenantId, null));
}
}
The return shapes:
-
Optional.empty()— fully omit the body (cmd_bodyends up as the empty-map sentinel). -
Optional.of(copy)— usecopyas the body source.
The implementation must return a new instance, never mutate this — the original cmd is still in flight when redactedForTracing() is called.
If the implementation throws, the framework falls back to publishing the un-redacted cmd and emits a warn on logger no.conta.command.tracing.Redaction starting with "Redactable.redactedForTracing threw" — alert on it.
See Sensitive data for guidance on choosing between Redactable and ExcludeCmdBody.
TenantIdAware / UserIdAware
Implemented automatically by TenantCommand and UserCommand respectively.
Can also be combined — a command can implement both TenantCommand and UserIdAware to carry both tenant and user context:
public class GenerateTokenCommand implements TenantCommand, UserIdAware {
private UUID cmdUuid;
private String cmdSourceRef;
private String tenantId;
private String userId;
// ...
}