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:
@@ -2,9 +2,13 @@ package cloud.nstart.terravault.ordinis.app;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "cloud.nstart.terravault.ordinis")
|
||||
@EnableJpaRepositories(basePackages = "cloud.nstart.terravault.ordinis")
|
||||
@EntityScan(basePackages = "cloud.nstart.terravault.ordinis")
|
||||
@EnableScheduling
|
||||
public class OrdinisApplication {
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,prometheus,refresh,metrics,env
|
||||
include: health,info,prometheus,refresh,metrics,env,mappings
|
||||
base-path: /actuator
|
||||
endpoint:
|
||||
health:
|
||||
@@ -23,7 +23,7 @@ management:
|
||||
liveness:
|
||||
include: livenessState
|
||||
readiness:
|
||||
include: readinessState
|
||||
include: readinessState,db
|
||||
prometheus:
|
||||
metrics:
|
||||
export:
|
||||
@@ -33,7 +33,6 @@ management:
|
||||
application: ${spring.application.name}
|
||||
environment: ${ENVIRONMENT:dev}
|
||||
|
||||
# Defaults для OTel (можно отключить через otel.sdk.disabled=true в dev)
|
||||
otel:
|
||||
exporter:
|
||||
otlp:
|
||||
@@ -53,8 +52,16 @@ logging:
|
||||
root: INFO
|
||||
cloud.nstart.terravault.ordinis: DEBUG
|
||||
|
||||
ordinis:
|
||||
outbox:
|
||||
enabled: ${ORDINIS_OUTBOX_ENABLED:true}
|
||||
poll-interval-ms: ${ORDINIS_OUTBOX_POLL_INTERVAL_MS:1000}
|
||||
batch-size: ${ORDINIS_OUTBOX_BATCH_SIZE:100}
|
||||
|
||||
---
|
||||
# DEV profile — local run без Cloud Config / Vault / OTel.
|
||||
# DEV profile: подключается к cluster PG+Kafka через kubectl port-forward.
|
||||
# Перед запуском: kubectl port-forward -n ordinis-dev svc/ordinis-postgres-rw 5432:5432 &
|
||||
# kubectl port-forward -n ordinis-dev svc/ordinis-kafka-kafka-bootstrap 9092:9092 &
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
@@ -66,17 +73,45 @@ spring:
|
||||
enabled: false
|
||||
autoconfigure:
|
||||
exclude:
|
||||
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
|
||||
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
|
||||
- org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
|
||||
- org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
|
||||
- org.springframework.cloud.vault.config.VaultAutoConfiguration
|
||||
- org.springframework.cloud.vault.config.VaultObservationAutoConfiguration
|
||||
- org.springframework.cloud.vault.config.VaultReactiveAutoConfiguration
|
||||
datasource:
|
||||
url: jdbc:postgresql://${ORDINIS_PG_HOST:localhost}:${ORDINIS_PG_PORT:5432}/${ORDINIS_PG_DB:ordinis}
|
||||
username: ${ORDINIS_PG_USER:ordinis_app}
|
||||
password: ${ORDINIS_PG_PASSWORD:}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
properties:
|
||||
hibernate.dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
hibernate.format_sql: true
|
||||
hibernate.jdbc.lob.non_contextual_creation: true
|
||||
open-in-view: false
|
||||
kafka:
|
||||
bootstrap-servers: ${ORDINIS_KAFKA_BOOTSTRAP:localhost:9092}
|
||||
properties:
|
||||
security.protocol: ${ORDINIS_KAFKA_SECURITY:SASL_PLAINTEXT}
|
||||
sasl.mechanism: ${ORDINIS_KAFKA_SASL_MECHANISM:SCRAM-SHA-512}
|
||||
sasl.jaas.config: >-
|
||||
org.apache.kafka.common.security.scram.ScramLoginModule required
|
||||
username="${ORDINIS_KAFKA_USER:ordinis-app}"
|
||||
password="${ORDINIS_KAFKA_PASSWORD:}";
|
||||
producer:
|
||||
acks: all
|
||||
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
value-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
properties:
|
||||
enable.idempotence: true
|
||||
compression.type: lz4
|
||||
|
||||
otel:
|
||||
sdk:
|
||||
disabled: true
|
||||
|
||||
---
|
||||
# K8s profile — реальная инфра: Spring Cloud Config + Vault Kubernetes auth.
|
||||
# K8s profile: реальная инфра в кластере. Cloud Config + Vault Kubernetes auth.
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
@@ -98,3 +133,7 @@ spring:
|
||||
enabled: true
|
||||
backend: secret
|
||||
application-name: ordinis/cuod
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
open-in-view: false
|
||||
|
||||
Reference in New Issue
Block a user