God entities¶
The corpus prior¶
Rails / Django / ActiveRecord tutorials are full of models that accrete fields. Order starts with customer_id and total, and three releases later it has shipping address fields, payment-method fields, line-item counters, delivery-status timestamps, customer notes, and an admin-override flag. Stack Overflow accepts this as normal — top answers to "how do I model an order" routinely show 15+ fields on one model.
LLMs follow the corpus. Given a spec describing orders, an agent emits one giant entity because that's what every "Rails order model" example in training did.
Wrong shape¶
entity Order "Order":
id: uuid pk
customer_email: str(200) required
customer_phone: str(20)
shipping_street: str(200)
shipping_city: str(100)
shipping_postcode: str(20)
shipping_country: str(2)
payment_method: enum[card, paypal, transfer]
payment_last4: str(4)
payment_status: enum[pending, paid, refunded]
delivery_carrier: str(50)
delivery_tracking: str(100)
delivery_eta: date
delivered_at: datetime
total: decimal(10,2)
line_count: int
notes: text
admin_override: bool=false
status: enum[draft, placed, shipped, delivered, cancelled]
...
What this gives up: every surface that touches Order has to deal with all 20+ fields. RBAC is overly broad — "can read Order" gives access to payment internals, customer PII, admin overrides. State transitions get tangled because the entity has multiple lifecycles (payment, delivery, fulfilment) braided together. Scope rules become brittle because "customer can see their orders" now has to reason about which subset of fields the customer should see. Schema evolution touches everyone.
Right shape¶
Each entity owns one coherent concern. Use ref to compose:
entity Order "Order":
id: uuid pk
customer: ref Customer required
shipping_address: ref Address
status: enum[draft, placed, shipped, delivered, cancelled]=draft
placed_at: datetime
total: decimal(10,2)
entity OrderLine "Order Line":
id: uuid pk
order: ref Order required
product: ref Product required
quantity: int required
unit_price: decimal(10,2) required
entity Payment "Payment":
id: uuid pk
order: ref Order required
method: enum[card, paypal, transfer]
status: enum[pending, paid, refunded]
amount: decimal(10,2)
entity Delivery "Delivery":
id: uuid pk
order: ref Order required
carrier: str(50)
tracking: str(100)
eta: date
delivered_at: datetime
Each piece now has its own surfaces, its own RBAC permits, its own scope rules, its own state machine. The Order surface for a customer can show "your order is shipped" without leaking payment internals. The admin payment-reconciliation surface can scope on Payment without dragging the whole Order entity through.
Why this matters here¶
Dazzle's DSL is built around the assumption that entities are coherent units of authority and lifecycle — RBAC permits attach to entities, scope rules traverse refs, state machines belong to entities. A god entity collapses all those axes onto one row and makes every downstream construct harder to reason about. The framework's value emerges when the entity boundaries match the conceptual boundaries; god entities erase that match.
A useful heuristic: if you can't write a coherent sentence about what one entity is without using the word "and" twice, decompose.
Cross-references¶
- Inference KB
no_god_entities— bootstrap auto-surfacing viaspec_analyze.propose_patterns. docs/reference/project-layout.md— entity decomposition examples.