Why structure matters more than tooling
A Minimal MDDE Project Structure — How to organize metadata, rules, SQL generation, and validation in practice.
Why structure matters more than tooling
Most architectural approaches fail not because the ideas are wrong, but because there is no clear place to put them.
- Guidelines float in documents.
- Rules live in ad-hoc scripts.
- SQL sits wherever it happened to be created.
MDDE takes a different stance:
Architecture should be visible in the project structure itself.
This article describes a minimal, opinionated MDDE project layout that has proven practical in real work — without over-engineering.

Design goals
The structure aims to be:
- Explicit — intent is easy to find
- Composable — pieces evolve independently
- Enforceable — rules live next to code
- Tool-agnostic — no framework lock-in
You should be able to adopt this incrementally.
The minimal MDDE layout
mdde-project/
│
├── metadata/
│ ├── entities.yaml
│ ├── attributes.yaml
│ ├── relationships.yaml
│ └── semantics.yaml
│
├── rules/
│ ├── sql/
│ │ ├── no_select_star.py
│ │ ├── explicit_joins.py
│ │ └── deterministic_filters.py
│ └── README.md
│
├── generation/
│ ├── templates/
│ │ ├── select.sql.j2
│ │ └── join.sql.j2
│ └── generate_sql.py
│
├── validation/
│ ├── regression/
│ │ ├── compare_results.py
│ │ └── compare_structure.py
│ └── validate.py
│
├── adr/
│ ├── ADR-001-parse-sql.md
│ ├── ADR-002-regression-testing.md
│ └── ADR-003-deterministic-generation.md
│
├── diagnostics/
│ └── runs/
│ └── 2025-02-01.json
│
└── README.md
Let’s walk through this piece by piece.
Metadata: the source of intent
metadata/
This is where architectural intent lives.
Not SQL.
Not rules.
Not code.
Metadata captures:
- What entities exist
- How they relate
- What semantics apply
Example (entities.yaml):
sales_summary:
grain: daily
source: sales_table
If something is not here, it is not part of the architecture.
Rules: executable guidelines
rules/
Rules implement architectural constraints.
Each rule:
- Enforces one guideline
- Has a clear scope
- Produces observable output
Example:
# rules/sql/no_select_star.py
class NoSelectStarRule:
def validate(self, ast):
if ast.select_clause.contains_star():
return "SELECT * is not allowed"
Rules never modify behavior silently.
They detect, explain, and enforce.
Generation: intent → SQL
generation/
SQL generation is treated as a rendering step.
Templates are:
- Deterministic
- Explicit
-
Reviewable
generate_sql.py
sql = generate_sql(
entity=”sales_summary”,
engine=”postgres”
)
Generated SQL is an output artifact , not a design surface.
Validation: comparison, not confidence
validation/
Validation answers:
- Is this behavior equivalent?
-
If not, where does it differ?
compare_structure(old_sql, new_sql)
compare_results(old_results, new_results)
Validation lives alongside generation — not after deployment.
ADRs: decisions with a home
adr/
Every ADR:
- Has a stable filename
- Is referenced by rules or generators
- Explains why , not how
This avoids architectural archaeology.
Diagnostics: architecture in motion
diagnostics/
Every run emits:
- Applied rules
- Violations
- Generated SQL hashes
-
Validation outcomes
{
“entity”: “sales_summary”,
“rules_passed”: 12,
“rules_failed”: 0,
“sql_hash”: “9a7f…”
}
Architecture becomes observable, not assumed.
Why this structure works
This layout enforces separation of concerns:

No single folder does everything.
No single artifact carries implicit meaning.
Start small
You don’t need all of this on day one.
You can start with:
metadata/rules/adr/
And add generation and validation as pressure increases.
The structure scales because intent scales before code.
What’s next
In the next articles, I’ll cover:
- Rule design patterns (what makes a good rule)
- How rules integrate with SQL generation
- CI pipelines for MDDE-style validation
- How to evolve metadata without breaking contracts
All continuing the same theme: architecture that is explicit, executable, and durable under change.