##### Status **ACCEPTED** - March 2, 2026 ##### Context When building the EF Core data model for Rempla, we needed to decide where to enforce data integrity rules — constraints like required fields, max lengths, unique indexes, and foreign key enforcement. The common default in EF Core projects is to push these constraints into the database via Fluent API configurations (`IsRequired()`, `HasMaxLength()`, `IsUnique()`, non-nullable FKs). This creates a "defense in depth" approach where the database acts as a final safety net. However, this approach has trade-offs: - Insert ordering becomes rigid — non-nullable FKs force specific creation sequences - Schema migrations become heavier — every rule change requires a database migration - Soft-delete and data lifecycle patterns fight against strict FK enforcement - The database becomes a second source of business rules, often duplicating application logic - Development friction increases — the database rejects data before the application can provide meaningful error messages ##### Decision Enforce all data validation and business rules in the **application layer**. The database serves as a store, not a rule enforcer. Specifically: - **No `IsRequired()`** — nullability is not enforced at the database level - **No `HasMaxLength()`** — length limits are validated in application code - **No `IsUnique()` indexes** — uniqueness is checked in application logic - **All foreign keys are nullable** (`int?`) — referential integrity is managed by the application - **No `HasKey()` for `Id` properties** — EF Core convention discovers these automatically - **Enums stored as int** — no string conversion overhead EF Core configurations only define what cannot be inferred by convention: - Relationship mappings (one-to-one, one-to-many, many-to-many) - Join table naming - Seed data for reference types ##### Rationale - **Single source of truth**: Business rules live in one place — the application. No silent database rejections that bypass application error handling. - **Flexibility**: Nullable FKs allow entities to be created in any order, simplify data imports, and support future soft-delete patterns without cascade complications. - **Simpler migrations**: Schema changes are less frequent and less disruptive when the database isn't encoding business rules. - **Developer experience**: Meaningful validation errors come from application code, not cryptic database constraint violations. - **Acceptable risk**: A bug could write invalid data. This is a conscious trade-off — caught by tests and application validation rather than database constraints. ##### Consequences - Application code must validate all inputs at system boundaries - Integration tests should verify data integrity rules - No database-level protection against orphaned records or invalid data from direct SQL access - Ops/support queries against the database should not assume data integrity guarantees --- **Decision Date**: March 2, 2026 **Author**: Lead Architect