feat: Maven multimodule scaffolding for Ordinis MDM service
- 10 модулей (domain, validation, events-api, outbox, rest-api, app, read-api, projection-writer, cuod-bundle, migrations) - Parent pom + BOM с Spring Boot 3.4.2, Spring Cloud 2024.0.0, Vault Config 4.2.0 - ordinis-app: Spring Boot main с Actuator/Prometheus/Logstash JSON encoder/OpenTelemetry/Cloud Config/Vault skeleton - ordinis-migrations: Liquibase changelogs (dictionary_definitions/records, audit_log, outbox_events, idempotency_keys, EXCLUSION CONSTRAINT через btree_gist) - Multi-stage Dockerfiles для app и migrations
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# Liquibase migration image — запускается Helm pre-upgrade Job (1-shot, без Spring Boot)
|
||||
FROM liquibase/liquibase:4.27.0
|
||||
|
||||
USER root
|
||||
COPY src/main/resources/db/changelog /liquibase/changelog
|
||||
USER liquibase
|
||||
|
||||
ENV LIQUIBASE_COMMAND_CHANGELOG_FILE=/liquibase/changelog/master.xml
|
||||
|
||||
# Helm Job переопределяет JDBC URL через ENV
|
||||
# CMD: ["update"] (default из base image работает с LIQUIBASE_COMMAND_*)
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>cloud.nstart.terravault.ordinis</groupId>
|
||||
<artifactId>ordinis-parent</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ordinis-migrations</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Ordinis :: Migrations</name>
|
||||
<description>Liquibase changelogs для Helm pre-upgrade Job. Собирается в отдельный Docker image (без Spring Boot).</description>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
|
||||
|
||||
<changeSet id="0001-1-extensions" author="ordinis" runAlways="true">
|
||||
<comment>Verify required PostgreSQL extensions are present (created by CNPG initdb).</comment>
|
||||
<preConditions onFail="HALT">
|
||||
<sqlCheck expectedResult="t">
|
||||
SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'postgis')
|
||||
</sqlCheck>
|
||||
<sqlCheck expectedResult="t">
|
||||
SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'btree_gist')
|
||||
</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>SELECT 1;</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
|
||||
|
||||
<changeSet id="0002-1-dictionary-definitions" author="ordinis">
|
||||
<comment>Каталог справочников: имя, JSON Schema, scope, версия.</comment>
|
||||
<createTable tableName="dictionary_definitions">
|
||||
<column name="id" type="UUID" defaultValueComputed="gen_random_uuid()">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(128)">
|
||||
<constraints nullable="false" unique="true"/>
|
||||
</column>
|
||||
<column name="display_name" type="VARCHAR(256)"/>
|
||||
<column name="description" type="TEXT"/>
|
||||
<column name="scope" type="VARCHAR(32)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="schema_json" type="JSONB">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="schema_version" type="VARCHAR(32)" defaultValue="1.0.0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="bundle" type="VARCHAR(64)" defaultValue="cuod">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_at" type="TIMESTAMPTZ" defaultValueComputed="NOW()">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="updated_at" type="TIMESTAMPTZ" defaultValueComputed="NOW()">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_by" type="VARCHAR(128)"/>
|
||||
<column name="updated_by" type="VARCHAR(128)"/>
|
||||
</createTable>
|
||||
|
||||
<sql>
|
||||
ALTER TABLE dictionary_definitions
|
||||
ADD CONSTRAINT dictionary_definitions_scope_check
|
||||
CHECK (scope IN ('public', 'internal', 'restricted'));
|
||||
</sql>
|
||||
|
||||
<createIndex indexName="idx_dict_def_bundle" tableName="dictionary_definitions">
|
||||
<column name="bundle"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
|
||||
|
||||
<changeSet id="0003-1-dictionary-records" author="ordinis">
|
||||
<comment>Bitemporal dictionary records: tx-time через Envers, effective-time через valid_from/valid_to.</comment>
|
||||
<createTable tableName="dictionary_records">
|
||||
<column name="id" type="UUID" defaultValueComputed="gen_random_uuid()">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="dictionary_id" type="UUID">
|
||||
<constraints nullable="false" foreignKeyName="fk_record_dictionary" references="dictionary_definitions(id)"/>
|
||||
</column>
|
||||
<column name="business_key" type="VARCHAR(256)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="data" type="JSONB">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="geometry" type="GEOMETRY(Geometry, 4326)"/>
|
||||
<column name="data_scope" type="VARCHAR(32)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="valid_from" type="TIMESTAMPTZ">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="valid_to" type="TIMESTAMPTZ" defaultValue="9999-12-31T23:59:59Z"/>
|
||||
<column name="created_at" type="TIMESTAMPTZ" defaultValueComputed="NOW()">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="updated_at" type="TIMESTAMPTZ" defaultValueComputed="NOW()">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_by" type="VARCHAR(128)"/>
|
||||
<column name="updated_by" type="VARCHAR(128)"/>
|
||||
</createTable>
|
||||
|
||||
<sql>
|
||||
ALTER TABLE dictionary_records
|
||||
ADD CONSTRAINT dictionary_records_scope_check
|
||||
CHECK (data_scope IN ('public', 'internal', 'restricted'));
|
||||
</sql>
|
||||
|
||||
<!-- EXCLUSION CONSTRAINT: запрещает overlapping valid периоды для одного business_key в одном словаре -->
|
||||
<sql>
|
||||
ALTER TABLE dictionary_records
|
||||
ADD CONSTRAINT dictionary_records_no_overlap
|
||||
EXCLUDE USING GIST (
|
||||
dictionary_id WITH =,
|
||||
business_key WITH =,
|
||||
tstzrange(valid_from, valid_to, '[)') WITH &&
|
||||
);
|
||||
</sql>
|
||||
|
||||
<createIndex indexName="idx_dict_records_dict" tableName="dictionary_records">
|
||||
<column name="dictionary_id"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="idx_dict_records_scope" tableName="dictionary_records">
|
||||
<column name="data_scope"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="idx_dict_records_business_key" tableName="dictionary_records">
|
||||
<column name="dictionary_id"/>
|
||||
<column name="business_key"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="idx_dict_records_valid_to" tableName="dictionary_records">
|
||||
<column name="valid_to"/>
|
||||
</createIndex>
|
||||
|
||||
<sql>
|
||||
CREATE INDEX idx_dict_records_geometry ON dictionary_records USING GIST (geometry);
|
||||
CREATE INDEX idx_dict_records_data_gin ON dictionary_records USING GIN (data jsonb_path_ops);
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
|
||||
|
||||
<changeSet id="0004-1-audit-log" author="ordinis">
|
||||
<comment>Юридически значимый audit log. Source of truth для compliance, отдельно от Loki.</comment>
|
||||
<createTable tableName="audit_log">
|
||||
<column name="id" type="BIGSERIAL">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="event_time" type="TIMESTAMPTZ" defaultValueComputed="NOW()">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="event_type" type="VARCHAR(64)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="user_id" type="VARCHAR(128)"/>
|
||||
<column name="user_scope" type="VARCHAR(32)"/>
|
||||
<column name="dictionary_id" type="UUID"/>
|
||||
<column name="dictionary_name" type="VARCHAR(128)"/>
|
||||
<column name="record_id" type="UUID"/>
|
||||
<column name="business_key" type="VARCHAR(256)"/>
|
||||
<column name="action" type="VARCHAR(32)"/>
|
||||
<column name="payload_before" type="JSONB"/>
|
||||
<column name="payload_after" type="JSONB"/>
|
||||
<column name="trace_id" type="VARCHAR(64)"/>
|
||||
<column name="request_id" type="VARCHAR(64)"/>
|
||||
<column name="ip_address" type="INET"/>
|
||||
<column name="user_agent" type="TEXT"/>
|
||||
</createTable>
|
||||
|
||||
<createIndex indexName="idx_audit_event_time" tableName="audit_log">
|
||||
<column name="event_time"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="idx_audit_user_id" tableName="audit_log">
|
||||
<column name="user_id"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="idx_audit_dictionary" tableName="audit_log">
|
||||
<column name="dictionary_id"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="idx_audit_trace" tableName="audit_log">
|
||||
<column name="trace_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
|
||||
|
||||
<changeSet id="0005-1-outbox-events" author="ordinis">
|
||||
<comment>Outbox pattern: события публикуются в Kafka @Scheduled poller'ом.</comment>
|
||||
<createTable tableName="outbox_events">
|
||||
<column name="id" type="BIGSERIAL">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="event_type" type="VARCHAR(64)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="aggregate_type" type="VARCHAR(64)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="aggregate_id" type="VARCHAR(256)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="dictionary_name" type="VARCHAR(128)"/>
|
||||
<column name="data_scope" type="VARCHAR(32)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="payload" type="JSONB">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="kafka_topic" type="VARCHAR(256)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="kafka_key" type="VARCHAR(512)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_at" type="TIMESTAMPTZ" defaultValueComputed="NOW()">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="published_at" type="TIMESTAMPTZ"/>
|
||||
<column name="trace_id" type="VARCHAR(64)"/>
|
||||
<column name="span_id" type="VARCHAR(64)"/>
|
||||
<column name="attempt_count" type="INTEGER" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="last_error" type="TEXT"/>
|
||||
</createTable>
|
||||
|
||||
<!-- Partial index: poller сканирует только unpublished -->
|
||||
<sql>
|
||||
CREATE INDEX idx_outbox_unpublished ON outbox_events (created_at)
|
||||
WHERE published_at IS NULL;
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
|
||||
|
||||
<changeSet id="0006-1-idempotency-keys" author="ordinis">
|
||||
<comment>Idempotency keys для exactly-once семантики write API. Retention 30 дней через cleanup job.</comment>
|
||||
<createTable tableName="idempotency_keys">
|
||||
<column name="key" type="VARCHAR(128)">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="request_hash" type="VARCHAR(64)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="response_status" type="INTEGER"/>
|
||||
<column name="response_body" type="JSONB"/>
|
||||
<column name="created_at" type="TIMESTAMPTZ" defaultValueComputed="NOW()">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="expires_at" type="TIMESTAMPTZ">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<createIndex indexName="idx_idempotency_expires" tableName="idempotency_keys">
|
||||
<column name="expires_at"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
|
||||
|
||||
<include file="db/changelog/changes/0001-extensions.xml"/>
|
||||
<include file="db/changelog/changes/0002-dictionary-definitions.xml"/>
|
||||
<include file="db/changelog/changes/0003-dictionary-records.xml"/>
|
||||
<include file="db/changelog/changes/0004-audit-log.xml"/>
|
||||
<include file="db/changelog/changes/0005-outbox-events.xml"/>
|
||||
<include file="db/changelog/changes/0006-idempotency-keys.xml"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user