feat(validation,rest-api,outbox): write-side end-to-end working
ordinis-validation:
- ValidationError, ValidationResult, ValidationException
- RecordValidator: networknt JSON Schema (Draft-7) + custom rule для
x-localized полей. Walk schema, для каждого x-localized:true property
проверяет: значение это object с locale keys (BCP 47 pattern xx-XX),
все ключи в supported_locales, default_locale обязательно присутствует,
значения non-empty strings.
ordinis-outbox:
- KafkaTopicResolver: ordinis.<bundle>.events.<scope>
- OutboxRecorder: запись события в outbox в той же транзакции (MANDATORY
propagation), с tracer для trace_id/span_id из MDC если OTel доступен
- OutboxPoller: @Scheduled, batch_size + poll_interval из config, метрики
ordinis_outbox_published_total / publish_errors_total / kafka_publish_duration_seconds
- OutboxLagGauge: ordinis_outbox_pending_events (gauge для алертов)
- ConditionalOnProperty ordinis.outbox.enabled — отключаем в read-api/projection-writer
ordinis-rest-api:
- DTOs: CreateDictionaryRequest, DictionaryResponse, CreateRecordRequest,
RecordResponse (с WKT serialization geometry)
- DictionaryDefinitionService: CRUD definitions, outbox events DefinitionCreated/Updated
- DictionaryRecordService: bitemporal write-side, SERIALIZABLE isolation,
bound check FAR_FUTURE valid_to. Update = close current + create new
(никогда in-place). Outbox event RecordCreated/Updated/Deleted.
- DictionaryDefinitionController: GET list/by-name, POST create, PUT update
- DictionaryRecordController: GET active/history, POST create, PUT update,
DELETE (close)
- OrdinisException + ApiErrorResponse + GlobalExceptionHandler
(422 validation, 404 not found, 409 conflict, 500 internal)
ordinis-app:
- @EnableJpaRepositories + @EntityScan для multi-package сканирования
- application.yml profile-based: dev (PG localhost через port-forward,
ddl-auto=update, OTel disabled), k8s (Cloud Config + Vault Kubernetes auth)
- spring.kafka producer config с SASL SCRAM-SHA-512
ordinis-domain:
- JpaAuditingConfig: добавлен DateTimeProvider возвращающий OffsetDateTime
(Spring Data Auditing по дефолту его не поддерживает, без него падает
IllegalArgumentException 'Cannot convert LocalDateTime to OffsetDateTime')
- @ConditionalOnBean(DataSource) убран — race с lifecycle ломал auditing
E2E test (port-forward к cluster PG+Kafka):
- POST dictionary 'ground_station' → 201, JSONB schema сохранена,
supported_locales = {ru-RU, en-US}, default_locale = ru-RU
- POST record MOSCOW-1 multi-locale name + geometry POINT(37.618 55.751) → 201
- POST record с en-US без ru-RU → 422 с двумя ошибками:
* networknt 'required property ru-RU not found'
* custom 'x-localized.missing_default Default locale ru-RU is required'
- outbox_events: 2 row (DefinitionCreated + RecordCreated), topic
ordinis.cuod.events.public, ключи правильные
- revinfo + dictionary_records_aud: 1 row (Envers tx-time history активен)
This commit is contained in:
+16
-10
@@ -1,27 +1,28 @@
|
||||
package cloud.nstart.terravault.ordinis.domain.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Включает {@code @CreatedBy}/{@code @LastModifiedBy}. Domain не знает откуда
|
||||
* приходит user identity — модули выше (app, rest-api) подключают свой
|
||||
* {@link AuditorAware} (JWT subject из @nstart/auth). Default тут возвращает
|
||||
* {@code "system"} для batch/internal операций.
|
||||
* Включает {@code @CreatedBy}/{@code @LastModifiedBy}/{@code @CreatedDate}/{@code @LastModifiedDate}.
|
||||
*
|
||||
* <p>{@link ConditionalOnBean} на {@link DataSource} — без БД (dev smoke
|
||||
* profile) auditing не активируется, JPA metamodel не строится.
|
||||
* <p>Domain не знает откуда приходит user identity — модули выше (app,
|
||||
* rest-api) подключают свой {@link AuditorAware} (JWT subject из @nstart/auth).
|
||||
* Default тут возвращает {@code "system"} для batch/internal операций.
|
||||
*
|
||||
* <p>{@link DateTimeProvider} явно возвращает {@link OffsetDateTime} — Spring
|
||||
* Data Auditing по дефолту не поддерживает {@code OffsetDateTime}, так что без
|
||||
* этого bean'а получим {@code IllegalArgumentException} при сохранении.
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnBean(DataSource.class)
|
||||
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
|
||||
@EnableJpaAuditing(auditorAwareRef = "auditorAware", dateTimeProviderRef = "auditingDateTimeProvider")
|
||||
public class JpaAuditingConfig {
|
||||
|
||||
@Bean
|
||||
@@ -29,4 +30,9 @@ public class JpaAuditingConfig {
|
||||
public AuditorAware<String> auditorAware() {
|
||||
return () -> Optional.of("system");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DateTimeProvider auditingDateTimeProvider() {
|
||||
return () -> Optional.of(OffsetDateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user