{"id":49580390,"url":"https://github.com/lazycodedoggy/evoschema","last_synced_at":"2026-05-03T19:05:43.359Z","repository":{"id":355439035,"uuid":"1220059896","full_name":"lazycodedoggy/EvoSchema","owner":"lazycodedoggy","description":"EvoSchema is a phase-based database evolution runner (Pre-DDL / DML / Assert / Post-DDL) for code-driven schema and data changes across multiple datasources. It solves the consistency challenge when multiple microservices need to evolve their own databases during a major release—without cross-service distributed transactions.","archived":false,"fork":false,"pushed_at":"2026-05-03T17:28:40.000Z","size":2322,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-03T17:29:45.040Z","etag":null,"topics":["atomikos","code-driven-migration","compensation-pattern","controlled-release","data-consistency","data-migration","database-evolution","database-migration","database-rollbacks","db-management","java","jta","microservices","migration-tool","release-automation","safe-migration","schema-migration","spring-boot","sql-guardrails","xa-transactions"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lazycodedoggy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T13:58:07.000Z","updated_at":"2026-05-03T17:28:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lazycodedoggy/EvoSchema","commit_stats":null,"previous_names":["lazycodedoggy/evoschema"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/lazycodedoggy/EvoSchema","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lazycodedoggy%2FEvoSchema","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lazycodedoggy%2FEvoSchema/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lazycodedoggy%2FEvoSchema/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lazycodedoggy%2FEvoSchema/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lazycodedoggy","download_url":"https://codeload.github.com/lazycodedoggy/EvoSchema/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lazycodedoggy%2FEvoSchema/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32581063,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T06:36:36.687Z","status":"ssl_error","status_checked_at":"2026-05-03T06:36:09.306Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["atomikos","code-driven-migration","compensation-pattern","controlled-release","data-consistency","data-migration","database-evolution","database-migration","database-rollbacks","db-management","java","jta","microservices","migration-tool","release-automation","safe-migration","schema-migration","spring-boot","sql-guardrails","xa-transactions"],"created_at":"2026-05-03T19:05:42.486Z","updated_at":"2026-05-03T19:05:43.350Z","avatar_url":"https://github.com/lazycodedoggy.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EvoSchema\n\n[![License](https://img.shields.io/github/license/lazycodedoggy/EvoSchema)](LICENSE)\n[![Version](https://img.shields.io/badge/version-1.0-blue)](pom.xml)\n\nEvoSchema is a code-driven database migration framework for microservices architectures, designed to ensure operational safety during major release rollouts.\n\nIt uses a phase-based execution model (Pre-DDL → DML / Script → Assert → Post-DDL) to manage schema and data evolution across one or more datasources, with built-in SQL guardrails, multi-datasource coordination (JTA/XA), and developer-defined rollback strategies.\n\nIt focuses on operational safety for major releases: explicit ordering, developer-defined compensation SQL for limited rollback, and SQL guardrails (DML-only, DML+query, query-only) to reduce accidental misuse.\n\n\u003e New here? Start with the guided demo: [Getting Started With TutorialOrderSyncDemo](docs/getting-started-tutorial-order-sync.md)\n\nQuick links:\n\n- [Getting Started With TutorialOrderSyncDemo](docs/getting-started-tutorial-order-sync.md)\n- [Chinese README](README.zh-CN.md)\n- [TutorialOrderSyncDemo.java](src/test/java/io/github/evoschema/dbscript/TutorialOrderSyncDemo.java)\n- [TutorialOrderSyncDemoTest.java](src/test/java/io/github/evoschema/TutorialOrderSyncDemoTest.java)\n\n![EvoSchema overview](assets/proj_desc.png)\n\n## What It Solves\n\nIn a microservice architecture, each service usually owns its own database schema. During a major version rollout, several services may need to evolve their schemas and data at roughly the same time.\n\nThis creates a hard engineering problem:\n\n- schema changes often need release-level consistency\n- databases are independent, so there is no shared cross-service transaction context\n- MySQL DDL is generally not rollback-friendly because of implicit commits\n\nEvoSchema does not try to solve this as a global distributed transaction platform.\n\nInstead, it provides a practical engineering model:\n\n- define migration logic in code\n- split execution into ordered phases\n- support multi-datasource execution\n- provide limited rollback through developer-defined compensation SQL\n- apply SQL guardrails to reduce accidental misuse\n\n## Project Positioning\n\nEvoSchema is:\n\n- a non-web Spring Boot application\n- a single-process migration executor\n- an annotation-driven database evolution framework\n- suitable for controlled release pipelines and internal migration runners\n\nEvoSchema is not:\n\n- a cross-microservice orchestration control plane\n- a distributed strong-consistency release system\n- a full SQL sandbox over every possible JDBC entry point\n- a generic Flyway/Liquibase replacement focused on pure SQL files\n\n## Core Execution Model\n\nThe framework executes one migration component at a time and follows this phase order:\n\n```text\nPre-DDL -\u003e DML / DBScript -\u003e DMLAssert -\u003e Post-DDL\n```\n\n### Pre-DDL\n\nUse `@DBPREDDL` for preparatory structural changes.\n\n- typically used for `CREATE`, `ALTER`, `RENAME`, permission changes, or compatibility preparation\n- each method returns two SQL strings:\n  - forward SQL\n  - compensation SQL\n- if a later phase fails, EvoSchema tries to execute compensation SQL in reverse order\n\n### DML\n\nUse `@DBDML` for standard data changes.\n\n- returns a list of SQL statements\n- current implementation only allows:\n  - `INSERT`\n  - `UPDATE`\n  - `DELETE`\n- `REPLACE`, `MERGE`, `CALL`, and DDL are rejected for this annotation\n\n### DBScript\n\nUse `@DBScript` for more complex migration logic in Java code.\n\n- receives restricted `JdbcTemplate` parameters via `@TargetDBTemplate`\n- allows common string-SQL entry points for:\n  - DML: `INSERT`, `UPDATE`, `DELETE`, `REPLACE`, `MERGE`, `CALL`\n  - query: `SELECT`, `SHOW`, `EXPLAIN`, `DESCRIBE`, `DESC`\n- rejects DDL through guarded template validation\n\n### DMLAssert\n\nUse `@DBDMLAssert` for release-time data assertions.\n\n- intended for consistency checks after DML / DBScript\n- receives query-only `JdbcTemplate`\n- allows:\n  - `SELECT`\n  - `SHOW`\n  - `EXPLAIN`\n  - `DESCRIBE`\n  - `DESC`\n- throws an exception when assertion logic fails\n\n### Post-DDL\n\nUse `@DBPOSTDDL` for cleanup or final structural changes.\n\n- usually contains irreversible operations such as column cleanup or final shape consolidation\n- returns forward SQL only\n- does not provide compensation SQL\n\n## Transaction and Rollback Model\n\nEvoSchema supports two transaction modes:\n\n- single datasource: local transaction\n- multiple datasources: Atomikos JTA/XA transaction\n\nImportant limitations:\n\n- rollback support is limited and phase-aware\n- `Pre-DDL` rollback depends on developer-provided compensation SQL\n- `Post-DDL` is not automatically rollbackable\n- database-native DDL rollback is still constrained by the underlying database\n\n## Current Technical Stack\n\n- Java 17\n- Spring Boot 3.5.x\n- Currently MySQL-only; support for mainstream databases such as PostgreSQL is planned in future releases\n- XA support is currently MySQL-only; broader database support will be expanded progressively\n\n## Installation\n\n### 1. Add the project\n\nBuild with Maven:\n\n```bash\nmvn clean package\n```\n\n### 2. Prepare runtime configuration\n\nThe application loads datasource configuration from:\n\n```text\nclasspath:${profiles.prefixpath}/db.properties\n```\n\nThe default runtime properties are in [`src/main/resources/application.properties`](src/main/resources/application.properties).\n\nRelevant keys:\n\n```properties\nspring.profiles.active=dev\nlogging.config=classpath:${profiles.prefixpath}/log4j2.xml\nspring.jta.atomikos.properties.max-timeout=3000000\nspring.jta.atomikos.properties.default-jta-timeout=3000000\n```\n\n## Datasource Configuration\n\nConfigure datasources in `db.properties` using this format:\n\n```properties\nevoschema.datasource.customer.driverClassName=com.mysql.cj.jdbc.Driver\nevoschema.datasource.customer.url=jdbc:mysql://127.0.0.1:3306/customer_db?useUnicode=true\u0026characterEncoding=utf8\u0026serverTimezone=UTC\nevoschema.datasource.customer.username=root\nevoschema.datasource.customer.password=123456\n\nevoschema.datasource.finance.driverClassName=com.mysql.cj.jdbc.Driver\nevoschema.datasource.finance.url=jdbc:mysql://127.0.0.1:3306/finance_db?useUnicode=true\u0026characterEncoding=utf8\u0026serverTimezone=UTC\nevoschema.datasource.finance.username=root\nevoschema.datasource.finance.password=123456\n```\n\n### Naming Rules\n\nFor datasource key `customer`, EvoSchema automatically registers:\n\n- `customerDataSource`\n- `customerJdbcTemplate`\n\nFor datasource key `finance`, EvoSchema automatically registers:\n\n- `financeDataSource`\n- `financeJdbcTemplate`\n\n## How To Write a Migration Component\n\nUse [`DBScriptTemplate.java`](src/main/java/io/github/evoschema/dbscript/DBScriptTemplate.java) as the scaffold.\n\n### Step 1. Copy the template\n\nCreate a new class and rename it, for example:\n\n```java\npackage io.github.evoschema.dbscript;\n\nimport com.google.common.collect.ImmutableList;\nimport io.github.evoschema.annotation.DBDML;\nimport io.github.evoschema.annotation.DBDMLAssert;\nimport io.github.evoschema.annotation.DBPOSTDDL;\nimport io.github.evoschema.annotation.DBPREDDL;\nimport io.github.evoschema.annotation.DBScript;\nimport io.github.evoschema.annotation.TargetDBTemplate;\nimport java.util.List;\nimport org.springframework.jdbc.core.JdbcTemplate;\nimport org.springframework.stereotype.Component;\n\n@Component(\"release_20260401\")\npublic class Release20260401Migration\n{\n    @DBPREDDL(order = 1, dataSource = \"customer\")\n    public List\u003cString\u003e preDDL()\n    {\n        return ImmutableList.of(\n                \"ALTER TABLE customer_orders ADD COLUMN archived TINYINT DEFAULT 0;\",\n                \"ALTER TABLE customer_orders DROP COLUMN archived;\"\n        );\n    }\n\n    @DBDML(order = 1, dataSource = \"customer\")\n    public List\u003cString\u003e dml()\n    {\n        return ImmutableList.of(\n                \"UPDATE customer_orders SET archived = 0 WHERE archived IS NULL\"\n        );\n    }\n\n    @DBScript(order = 2)\n    public void script(\n            @TargetDBTemplate(dataSource = \"customer\") JdbcTemplate customerTemplate,\n            @TargetDBTemplate(dataSource = \"finance\") JdbcTemplate financeTemplate)\n    {\n        Long count = customerTemplate.queryForObject(\n                \"SELECT COUNT(1) FROM customer_orders WHERE archived = 0\",\n                Long.class\n        );\n        if (count != null \u0026\u0026 count \u003e 0) {\n            financeTemplate.update(\n                    \"UPDATE finance_orders SET sync_status = 'READY' WHERE sync_status IS NULL\"\n            );\n        }\n    }\n\n    @DBDMLAssert(order = 3)\n    public void dmlAssert(@TargetDBTemplate(dataSource = \"customer\") JdbcTemplate customerTemplate)\n    {\n        Long count = customerTemplate.queryForObject(\n                \"SELECT COUNT(1) FROM customer_orders WHERE archived IS NULL\",\n                Long.class\n        );\n        if (count != null \u0026\u0026 count \u003e 0) {\n            throw new IllegalStateException(\"archived column still contains null values\");\n        }\n    }\n\n    @DBPOSTDDL(order = 1, dataSource = \"customer\")\n    public List\u003cString\u003e postDDL()\n    {\n        return ImmutableList.of(\n                \"ALTER TABLE customer_orders MODIFY COLUMN archived TINYINT NOT NULL DEFAULT 0\"\n        );\n    }\n}\n```\n\n### Step 2. Add `@Component`\n\nThe component name is the runtime entry id.\n\nFor example:\n\n```java\n@Component(\"release_20260401\")\n```\n\n### Step 3. Choose datasources by name\n\nUse `dataSource = \"customer\"` or inject templates with:\n\n```java\n@TargetDBTemplate(dataSource = \"customer\")\n```\n\nThe datasource name must match the keys in `db.properties`.\n\n## Annotation Reference\n\n### `@DBPREDDL`\n\n- phase: before DML\n- returns: exactly 2 SQL statements\n- purpose: forward DDL + compensation DDL\n\nExample:\n\n```java\n@DBPREDDL(order = 1, dataSource = \"customer\")\npublic List\u003cString\u003e preDDL()\n{\n    return ImmutableList.of(\n            \"ALTER TABLE customer_orders ADD COLUMN ext_id BIGINT DEFAULT NULL\",\n            \"ALTER TABLE customer_orders DROP COLUMN ext_id\"\n    );\n}\n```\n\n### `@DBDML`\n\n- phase: DML\n- returns: list of standard DML SQL\n- allowed first keywords:\n  - `INSERT`\n  - `UPDATE`\n  - `DELETE`\n\nExample:\n\n```java\n@DBDML(order = 1, dataSource = \"customer\")\npublic List\u003cString\u003e fixData()\n{\n    return ImmutableList.of(\n            \"UPDATE customer_orders SET status = 'READY' WHERE status = 'NEW'\"\n    );\n}\n```\n\n### `@DBScript`\n\n- phase: DML\n- style: Java logic with restricted `JdbcTemplate`\n- use when SQL alone is not expressive enough\n\nExample:\n\n```java\n@DBScript(order = 2)\npublic void sync(\n        @TargetDBTemplate(dataSource = \"customer\") JdbcTemplate customerTemplate,\n        @TargetDBTemplate(dataSource = \"finance\") JdbcTemplate financeTemplate)\n{\n    List\u003cLong\u003e orderIds = customerTemplate.queryForList(\n            \"SELECT order_id FROM customer_orders WHERE status = 'READY'\",\n            Long.class\n    );\n    for (Long orderId : orderIds) {\n        financeTemplate.update(\n                \"UPDATE finance_orders SET sync_status = 'SYNCED' WHERE order_id = ?\",\n                orderId\n        );\n    }\n}\n```\n\n### `@DBDMLAssert`\n\n- phase: after DML / DBScript\n- style: query-only assertion logic\n- purpose: fail fast when expected data state is not satisfied\n\nExample:\n\n```java\n@DBDMLAssert(order = 3)\npublic void assertResult(@TargetDBTemplate(dataSource = \"customer\") JdbcTemplate template)\n{\n    Long count = template.queryForObject(\n            \"SELECT COUNT(1) FROM customer_orders WHERE status = 'READY'\",\n            Long.class\n    );\n    if (count == null || count == 0L) {\n        throw new IllegalStateException(\"no READY records found\");\n    }\n}\n```\n\n### `@DBPOSTDDL`\n\n- phase: final structural cleanup\n- returns: forward SQL only\n\nExample:\n\n```java\n@DBPOSTDDL(order = 1, dataSource = \"customer\")\npublic List\u003cString\u003e postDDL()\n{\n    return ImmutableList.of(\n            \"ALTER TABLE customer_orders DROP COLUMN old_status\"\n    );\n}\n```\n\n## How To Run\n\nRun by component name:\n\n```bash\nmvn -q -DskipTests compile\nmvn -q exec:java -Dexec.args=\"release_20260401\"\n```\n\nOr invoke the starter directly:\n\n```java\nStarter.main(new String[] { \"release_20260401\" });\n```\n\nIf no argument is passed, the entry component defaults to the current date in `yyyyMMdd` format.\n\n## SQL Guardrails\n\nCurrent SQL restrictions are:\n\n### `@DBDML`\n\n- allowed:\n  - `INSERT`\n  - `UPDATE`\n  - `DELETE`\n- rejected:\n  - `REPLACE`\n  - `MERGE`\n  - `CALL`\n  - DDL\n\n### `@DBScript`\n\n- allowed:\n  - `INSERT`\n  - `UPDATE`\n  - `DELETE`\n  - `REPLACE`\n  - `MERGE`\n  - `CALL`\n  - `SELECT`\n  - `SHOW`\n  - `EXPLAIN`\n  - `DESCRIBE`\n  - `DESC`\n- rejected:\n  - DDL\n\n### `@DBDMLAssert`\n\n- allowed:\n  - `SELECT`\n  - `SHOW`\n  - `EXPLAIN`\n  - `DESCRIBE`\n  - `DESC`\n- rejected:\n  - DML\n  - DDL\n\nNote:\n\n- the guard currently focuses on common string-SQL `JdbcTemplate` entry points\n- it is not a full sandbox over every possible JDBC API\n\n## Recommended Release Pattern\n\nFor a multi-step change, prefer this pattern:\n\n1. use `Pre-DDL` to add compatible schema\n2. use `DBDML` / `DBScript` to backfill or transform data\n3. use `DBDMLAssert` to verify correctness\n4. use `Post-DDL` to finalize structure\n\nThis pattern reduces rollout risk and makes failures easier to understand and handle.\n\n## Testing\n\nThe repository includes both integration tests and unit tests.\n\nCurrent test coverage focuses on:\n\n- happy-path execution of all migration phases\n- rollback of completed `Pre-DDL` when a later `Pre-DDL` fails\n- rollback of DML / DBScript plus compensation of `Pre-DDL`\n- SQL guard behavior\n- guarded `JdbcTemplate` behavior\n- dynamic singleton lifecycle for manually registered datasource beans\n\nRun all tests:\n\n```bash\nmvn -q test\n```\n\nRepresentative test classes:\n\n- [TutorialOrderSyncDemoTest](src/test/java/io/github/evoschema/TutorialOrderSyncDemoTest.java)\n- [RollbackOnPreDdlFailureDemoTest](src/test/java/io/github/evoschema/RollbackOnPreDdlFailureDemoTest.java)\n- [RollbackOnDmlFailureDemoTest](src/test/java/io/github/evoschema/RollbackOnDmlFailureDemoTest.java)\n- [SqlStatementGuardTest](src/test/java/io/github/evoschema/SqlStatementGuardTest.java)\n- [GuardedJdbcTemplateTest](src/test/java/io/github/evoschema/GuardedJdbcTemplateTest.java)\n- [SpringBeanFactoryTest](src/test/java/io/github/evoschema/SpringBeanFactoryTest.java)\n\n## Limitations\n\n- EvoSchema coordinates one migration component per process run\n- rollback is limited to what the framework and your compensation SQL can express\n- `Post-DDL` is not rollbackable by design\n\n## License\n\nThis project is licensed under the MIT License.\n\nSee [`LICENSE`](LICENSE) for the full text.\n- it does not coordinate multiple microservices as a central orchestrator\n- it does not guarantee global release atomicity across independent services\n\n## When To Use EvoSchema\n\nUse it when you need:\n\n- code-reviewed database evolution logic\n- explicit phase ordering\n- controlled multi-datasource migration execution\n- release-time assertions\n- a pragmatic alternative to fully manual migration playbooks\n\n## Contributing\n\nRecommended contribution checklist:\n\n1. keep migration behavior explicit and easy to audit\n2. add or update tests for new execution paths\n3. avoid widening SQL permissions without test coverage\n4. document new datasource or runtime conventions in this README\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flazycodedoggy%2Fevoschema","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flazycodedoggy%2Fevoschema","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flazycodedoggy%2Fevoschema/lists"}