Adopting Immutability: Why We Removed Our Update Endpoints
In our work on the SBSofiaBartoli/crm-saas-backend project, we recently reached a significant milestone in our data architecture. We decided to pivot away from traditional update-heavy CRUD operations, favoring an immutable approach to record management.
The Problem with Traditional Updates
In our initial implementation, we treated records as mutable entities. Any change meant performing an UPDATE operation on an existing row. While this is the textbook way to handle CRUD, it created "ghost" states. We found ourselves constantly debugging why a field was in a specific state without a clear audit trail of how it arrived there. The system felt fragile because the current state destroyed the previous one.
Moving to Immutability
We decided that for our core business entities, history matters more than storage space. Instead of updating existing records, we now treat them as immutable. When a business requirement changes, we insert a new record representing the new state, effectively deprecating the old one.
In our NestJS services, this looks like moving from a patch or put approach to a strictly create workflow:
// Before: Mutable approach
async updateRecord(id: string, data: UpdateDto): Promise<Record> {
return this.repository.update(id, data);
}
// After: Immutable approach
async createRecordVersion(previousId: string, data: CreateDto): Promise<Record> {
const newRecord = this.factory.createNewVersion(previousId, data);
return this.repository.save(newRecord);
}
The Benefits
By adopting this pattern within our Hexagonal Architecture, we gained three major advantages:
- Full Auditability: We no longer need complex audit logs; the database history is the audit log.
- Simplified Logic: We removed entire controllers and DTOs responsible for partial updates, reducing our maintenance surface area.
- Consistency: Because records are never mutated, we avoid race conditions during concurrent updates.
The Takeaway
If you find your application logic bogged down by complex state management and validation for updates, consider if your entities actually need to change. Often, the best way to manage state is to treat it as a sequence of events rather than a single record. Start by identifying one core entity in your domain and transition it to an insert-only model to see the difference in your stability.
Generated with Gitvlg.com