feat(read-api): locale negotiation + scope filtering + multi-locale flatten
ordinis-read-api модуль полностью реализован:
- OrdinisReadApiApplication main + EnableJpaRepositories/EntityScan
- LocaleNegotiator: RFC 7231 / Locale.LanguageRange.parse + Locale.lookup
по supported_locales справочника, fallback на default_locale,
поддержка Accept-Language: * (raw mode)
- LocaleAwareJsonFlattener: walk schema_json.properties, для x-localized
полей в data заменяет {locale:text} на string выбранной локали
с fallback на default_locale; recursive walk для nested objects
- RecordReadService: read с scope filtering + locale negotiation +
flatten, метрика ordinis_read_api_query_duration_seconds, scope
access enforcement (ScopeAccessDeniedException)
- DictionaryReadController: GET /api/v1/{name}/records/{businessKey}
и list, with @RequestHeader(Accept-Language), Content-Language
response header, Vary: Accept-Language; query param as_scope для
v1 (заменим на JWT scope claim позже)
- ReadApiExceptionHandler: 404 NoSuchElement, 403 ScopeAccessDenied,
400 IllegalArgument, 500 fallback
- application.yml profile-based: dev (port-forward, ddl-auto=none —
read-api не валидирует schema, это work writer/migrations), k8s
(Vault role ordinis-read-api, hikari read-only=true)
- logback-spring.xml: MDC включает 'locale' для Loki query
E2E (port 8081 параллельно с writer на 8080):
- GET MOSCOW-1 без Accept-Language → name=Москва-1, Content-Language: ru-RU
- GET MOSCOW-1 с Accept-Language: en-US → name=Moscow-1, Content-Language: en-US
- GET MOSCOW-1 с Accept-Language: fr,en-US;q=0.8,ru;q=0.5 → выбрал en-US (fr нет в supported)
- GET MOSCOW-1 с Accept-Language: * → raw {ru-RU,en-US}, _meta.locale=null
- GET list → массив с flatten для каждой записи
- GET NONEXISTENT → 404 not_found
- GET с as_scope=INTERNAL для PUBLIC записи → 403 scope_access_denied
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
spring:
|
||||
application:
|
||||
name: ordinis-read-api
|
||||
profiles:
|
||||
active: ${SPRING_PROFILES_ACTIVE:dev}
|
||||
|
||||
server:
|
||||
port: 8081
|
||||
shutdown: graceful
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,prometheus,refresh,metrics,env,mappings
|
||||
endpoint:
|
||||
health:
|
||||
probes:
|
||||
enabled: true
|
||||
show-details: when-authorized
|
||||
group:
|
||||
liveness:
|
||||
include: livenessState
|
||||
readiness:
|
||||
include: readinessState,db
|
||||
prometheus:
|
||||
metrics:
|
||||
export:
|
||||
enabled: true
|
||||
metrics:
|
||||
tags:
|
||||
application: ${spring.application.name}
|
||||
environment: ${ENVIRONMENT:dev}
|
||||
|
||||
otel:
|
||||
exporter:
|
||||
otlp:
|
||||
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:http://tempo.monitoring:4317}
|
||||
resource:
|
||||
attributes:
|
||||
service.name: ${spring.application.name}
|
||||
service.namespace: ordinis
|
||||
deployment.environment: ${ENVIRONMENT:dev}
|
||||
traces:
|
||||
sampler: parentbased_traceidratio
|
||||
sampler.arg: ${ORDINIS_TRACING_SAMPLING:0.1}
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
cloud.nstart.terravault.ordinis: DEBUG
|
||||
|
||||
---
|
||||
# DEV: подключение к replica через port-forward.
|
||||
# Для read-api используем `ordinis-postgres-ro` сервис (read-only replica).
|
||||
# Через port-forward в local dev можно использовать тот же ordinis-postgres-rw
|
||||
# (single-node cluster, replica = тот же primary), важна только конвенция.
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev
|
||||
cloud:
|
||||
config:
|
||||
enabled: false
|
||||
vault:
|
||||
enabled: false
|
||||
autoconfigure:
|
||||
exclude:
|
||||
- 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:5433}/${ORDINIS_PG_DB:ordinis}
|
||||
username: ${ORDINIS_PG_USER:ordinis_app}
|
||||
password: ${ORDINIS_PG_PASSWORD:}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
hikari:
|
||||
read-only: true
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
properties:
|
||||
hibernate.dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
open-in-view: false
|
||||
|
||||
otel:
|
||||
sdk:
|
||||
disabled: true
|
||||
|
||||
---
|
||||
# K8s: подключение к replica + Vault для credentials, Cloud Config для feature flags.
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: k8s
|
||||
import:
|
||||
- "configserver:${SPRING_CLOUD_CONFIG_URI:http://config-server.ordinis-dev:8888}"
|
||||
- "vault://"
|
||||
cloud:
|
||||
config:
|
||||
enabled: true
|
||||
vault:
|
||||
enabled: true
|
||||
uri: ${VAULT_URI:http://vault.config:8200}
|
||||
authentication: KUBERNETES
|
||||
kubernetes:
|
||||
role: ordinis-read-api
|
||||
service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
kv:
|
||||
enabled: true
|
||||
backend: secret
|
||||
application-name: ordinis/cuod
|
||||
datasource:
|
||||
hikari:
|
||||
read-only: true
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
open-in-view: false
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<springProperty scope="context" name="appName" source="spring.application.name" defaultValue="ordinis-read-api"/>
|
||||
<springProperty scope="context" name="environment" source="ENVIRONMENT" defaultValue="dev"/>
|
||||
|
||||
<appender name="STDOUT_JSON" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
|
||||
<includeMdcKeyName>trace_id</includeMdcKeyName>
|
||||
<includeMdcKeyName>span_id</includeMdcKeyName>
|
||||
<includeMdcKeyName>scope</includeMdcKeyName>
|
||||
<includeMdcKeyName>dictionary</includeMdcKeyName>
|
||||
<includeMdcKeyName>locale</includeMdcKeyName>
|
||||
<includeMdcKeyName>request_id</includeMdcKeyName>
|
||||
<includeMdcKeyName>user_id</includeMdcKeyName>
|
||||
<customFields>{"service":"${appName}","environment":"${environment}"}</customFields>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT_HUMAN" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<springProfile name="dev">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT_HUMAN"/>
|
||||
</root>
|
||||
<logger name="cloud.nstart.terravault.ordinis" level="DEBUG"/>
|
||||
</springProfile>
|
||||
|
||||
<springProfile name="!dev">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT_JSON"/>
|
||||
</root>
|
||||
<logger name="cloud.nstart.terravault.ordinis" level="DEBUG"/>
|
||||
</springProfile>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user