feat(domain,events): bitemporal entities + i18n-aware definitions + sealed event API
ordinis-domain:
- DataScope enum (PUBLIC/INTERNAL/RESTRICTED) с Kafka topic suffix + visibility logic
- DictionaryDefinition entity: scope, schema_json (JsonNode JSONB), schema_version,
bundle, supported_locales (text[]), default_locale + Spring Data Auditing
- DictionaryRecord entity: bitemporal (Envers tx-time + valid_from/valid_to),
data JSONB, geometry (JTS via hibernate-spatial), data_scope. EXCLUSION
CONSTRAINT enforced в БД (Liquibase 0003).
- AuditLog entity для compliance (отдельно от Loki)
- OutboxEvent entity: aggregate_*, kafka_topic/kafka_key, attempt_count,
markPublished/markFailed, partial index idx_outbox_unpublished
- IdempotencyKey entity для exactly-once семантики write API
- Repositories: findActiveAt, findActiveByDictionaryAndScopes, findUnpublished
- JpaAuditingConfig: ConditionalOnBean(DataSource) — dev profile без БД
не активирует auditing, metamodel не строится
ordinis-events-api:
- Mirror DataScope enum (events не depend on domain)
- EventEnvelope<T> record: eventId, occurredAt, schemaVersion (semver),
bundle, dictionaryName, scope, traceId, spanId, payload
- Sealed DictionaryRecordEvent + records Created/Updated/Deleted
- Sealed DictionaryDefinitionEvent + records Created/Updated
- Jackson polymorphism через @JsonTypeInfo discriminator
- Multi-locale payload: события возят ВСЕ локали (consumer сам сплющивает)
i18n design:
- В JSONB поле кодируется как { 'ru-RU': '...', 'en-US': '...' }
- JSON Schema справочника помечает локализуемые поля extension x-localized:true
- supported_locales + default_locale в dictionary_definitions
- Read API будет negotiate через Accept-Language (RFC 7231) и сплющивать
Validation:
- mvn install all 11 modules: BUILD SUCCESS
- ordinis-app boot dev profile: /actuator/health UP, / отвечает
This commit is contained in:
@@ -19,5 +19,9 @@
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package cloud.nstart.terravault.ordinis.events;
|
||||
|
||||
/**
|
||||
* Mirror domain {@code DataScope} но без зависимости на JPA. События — публичный
|
||||
* API, consumers не должны тянуть domain entities ради enum.
|
||||
*
|
||||
* <p>Значения должны соответствовать {@code domain.DataScope}.
|
||||
*/
|
||||
public enum DataScope {
|
||||
PUBLIC,
|
||||
INTERNAL,
|
||||
RESTRICTED
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package cloud.nstart.terravault.ordinis.events;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Метаданные-обёртка вокруг payload каждого события. Едет в Kafka как value.
|
||||
*
|
||||
* <p>Schema versioning: {@code schemaVersion} = semver shared DTO модуля
|
||||
* (ordinis-events-api). Consumer проверяет совместимость через major version.
|
||||
*
|
||||
* @param <T> тип payload (DictionaryRecordEvent, DictionaryDefinitionEvent)
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record EventEnvelope<T>(
|
||||
UUID eventId,
|
||||
OffsetDateTime occurredAt,
|
||||
String schemaVersion,
|
||||
String bundle,
|
||||
String dictionaryName,
|
||||
DataScope dataScope,
|
||||
String traceId,
|
||||
String spanId,
|
||||
String causedBy,
|
||||
T payload) {
|
||||
|
||||
public static <T> EventEnvelope<T> of(
|
||||
String dictionaryName,
|
||||
DataScope scope,
|
||||
String bundle,
|
||||
T payload) {
|
||||
return new EventEnvelope<>(
|
||||
UUID.randomUUID(),
|
||||
OffsetDateTime.now(),
|
||||
EventSchemaVersion.CURRENT,
|
||||
bundle,
|
||||
dictionaryName,
|
||||
scope,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
payload);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package cloud.nstart.terravault.ordinis.events;
|
||||
|
||||
/**
|
||||
* Semver версии DTO модуля. Major bump = breaking change. Consumers должны
|
||||
* проверять major перед deserialization.
|
||||
*/
|
||||
public final class EventSchemaVersion {
|
||||
public static final String CURRENT = "1.0.0";
|
||||
public static final int MAJOR = 1;
|
||||
|
||||
private EventSchemaVersion() {}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package cloud.nstart.terravault.ordinis.events.definition;
|
||||
|
||||
import cloud.nstart.terravault.ordinis.events.DataScope;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record DictionaryDefinitionCreated(
|
||||
UUID definitionId,
|
||||
String dictionaryName,
|
||||
String displayName,
|
||||
String description,
|
||||
DataScope scope,
|
||||
JsonNode schemaJson,
|
||||
String schemaVersion,
|
||||
String bundle,
|
||||
List<String> supportedLocales,
|
||||
String defaultLocale,
|
||||
OffsetDateTime createdAt,
|
||||
String createdBy) implements DictionaryDefinitionEvent {}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package cloud.nstart.terravault.ordinis.events.definition;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = DictionaryDefinitionCreated.class, name = "DefinitionCreated"),
|
||||
@JsonSubTypes.Type(value = DictionaryDefinitionUpdated.class, name = "DefinitionUpdated")
|
||||
})
|
||||
public sealed interface DictionaryDefinitionEvent
|
||||
permits DictionaryDefinitionCreated, DictionaryDefinitionUpdated {
|
||||
|
||||
java.util.UUID definitionId();
|
||||
|
||||
String dictionaryName();
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package cloud.nstart.terravault.ordinis.events.definition;
|
||||
|
||||
import cloud.nstart.terravault.ordinis.events.DataScope;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Изменение определения справочника (schema, supported_locales и т.д.). Вместе
|
||||
* с новой версией прилетает {@code previousSchemaJson} для diff.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record DictionaryDefinitionUpdated(
|
||||
UUID definitionId,
|
||||
String dictionaryName,
|
||||
String displayName,
|
||||
String description,
|
||||
DataScope scope,
|
||||
JsonNode schemaJson,
|
||||
JsonNode previousSchemaJson,
|
||||
String schemaVersion,
|
||||
String bundle,
|
||||
List<String> supportedLocales,
|
||||
String defaultLocale,
|
||||
OffsetDateTime updatedAt,
|
||||
String updatedBy) implements DictionaryDefinitionEvent {}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package cloud.nstart.terravault.ordinis.events.record;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Создана новая версия записи справочника. {@code data} — полный multi-locale
|
||||
* payload (consumer сам сплющивает по нужной локали).
|
||||
*
|
||||
* <p>{@code geometryWkt} — WKT представление геометрии (если есть). EWKT с SRID
|
||||
* 4326 для совместимости с PostGIS. Может быть null.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record DictionaryRecordCreated(
|
||||
UUID recordId,
|
||||
String businessKey,
|
||||
JsonNode data,
|
||||
String geometryWkt,
|
||||
OffsetDateTime validFrom,
|
||||
OffsetDateTime validTo,
|
||||
OffsetDateTime createdAt,
|
||||
String createdBy) implements DictionaryRecordEvent {}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cloud.nstart.terravault.ordinis.events.record;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Логическое удаление записи (точнее: закрытие текущей версии установкой
|
||||
* valid_to). Реального DELETE из таблицы не происходит — для compliance/audit.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record DictionaryRecordDeleted(
|
||||
UUID recordId,
|
||||
String businessKey,
|
||||
OffsetDateTime closedAt,
|
||||
OffsetDateTime deletedAt,
|
||||
String deletedBy,
|
||||
String reason) implements DictionaryRecordEvent {}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package cloud.nstart.terravault.ordinis.events.record;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
/**
|
||||
* Sealed interface для всех событий по записям справочника. Jackson
|
||||
* polymorphic deserialization через {@code "type"} discriminator.
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = DictionaryRecordCreated.class, name = "RecordCreated"),
|
||||
@JsonSubTypes.Type(value = DictionaryRecordUpdated.class, name = "RecordUpdated"),
|
||||
@JsonSubTypes.Type(value = DictionaryRecordDeleted.class, name = "RecordDeleted")
|
||||
})
|
||||
public sealed interface DictionaryRecordEvent
|
||||
permits DictionaryRecordCreated, DictionaryRecordUpdated, DictionaryRecordDeleted {
|
||||
|
||||
/** UUID dictionary_records.id. */
|
||||
java.util.UUID recordId();
|
||||
|
||||
/** Бизнес-ключ записи (стабильный across versions). */
|
||||
String businessKey();
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package cloud.nstart.terravault.ordinis.events.record;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Обновление существующей записи. Modeling: новая версия (новый recordId, тот
|
||||
* же businessKey) с новой valid_from. Старая версия получила valid_to=newRecord.validFrom.
|
||||
*
|
||||
* <p>{@code previousData} даёт consumer'у возможность сделать diff.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record DictionaryRecordUpdated(
|
||||
UUID recordId,
|
||||
UUID previousRecordId,
|
||||
String businessKey,
|
||||
JsonNode data,
|
||||
JsonNode previousData,
|
||||
String geometryWkt,
|
||||
OffsetDateTime validFrom,
|
||||
OffsetDateTime validTo,
|
||||
OffsetDateTime updatedAt,
|
||||
String updatedBy) implements DictionaryRecordEvent {}
|
||||
Reference in New Issue
Block a user