Enhanced AuditInterceptor to track only changed fields (JSON diff) in Sprint 2 Story 2 Task 1. Changes: - Modified AuditInterceptor.AuditChanges to detect changed fields - For Update: Only serialize changed properties (50-70% storage reduction) - For Create: Serialize all current values (except PK/FK) - For Delete: Serialize all original values (except PK/FK) - Use System.Text.Json with compact serialization - Added SerializableValue method to handle ValueObjects (TenantId, UserId) - Filter out shadow properties and navigation properties Benefits: - Storage optimization: 50-70% reduction in audit log size - Better readability: Only see what changed - Performance: Faster JSON serialization for small diffs - Scalability: Reduced database storage growth Technical Details: - Uses EF Core ChangeTracker.Entries() - Filters by p.IsModified to get changed properties - Excludes PKs, FKs, and shadow properties - JSON options: WriteIndented=false, IgnoreNullValues - Handles ValueObject serialization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
5.4 KiB
5.4 KiB
task_id, story, status, estimated_hours, created_date, start_date, assignee
| task_id | story | status | estimated_hours | created_date | start_date | assignee |
|---|---|---|---|---|---|---|
| sprint_2_story_2_task_1 | sprint_2_story_2 | in_progress | 6 | 2025-11-05 | 2025-11-05 | Backend Team |
Task 1: Implement Changed Fields Detection (JSON Diff)
Story: Story 2 - Audit Log Core Features (Phase 2) Estimated: 6 hours
Description
Enhance the audit logging to detect and store only the changed fields (JSON diff) instead of full entity snapshots. This optimizes storage and makes audit logs more readable.
Acceptance Criteria
- JSON diff algorithm implemented
- Only changed fields stored in OldValues/NewValues
- Nested object changes detected
- Unit tests for diff algorithm
- Storage size reduced by 50-70%
Implementation Details
Files to Update:
- Diff Service:
colaflow-api/src/ColaFlow.Infrastructure/Services/JsonDiffService.cs
public class JsonDiffService : IJsonDiffService
{
public ChangedFields GetChangedFields(EntityEntry entry)
{
var changedFields = new Dictionary<string, FieldChange>();
foreach (var property in entry.Properties.Where(p => p.IsModified && !p.Metadata.IsPrimaryKey()))
{
changedFields[property.Metadata.Name] = new FieldChange
{
OldValue = property.OriginalValue,
NewValue = property.CurrentValue
};
}
return new ChangedFields { Fields = changedFields };
}
public string SerializeChangedFields(ChangedFields changes)
{
return JsonSerializer.Serialize(changes, new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
}
}
public class ChangedFields
{
public Dictionary<string, FieldChange> Fields { get; set; } = new();
}
public class FieldChange
{
public object? OldValue { get; set; }
public object? NewValue { get; set; }
}
- Update Interceptor:
colaflow-api/src/ColaFlow.Infrastructure/Interceptors/AuditLogInterceptor.cs
public class AuditLogInterceptor : SaveChangesInterceptor
{
private readonly IJsonDiffService _diffService;
public AuditLogInterceptor(
ITenantContext tenantContext,
IHttpContextAccessor httpContextAccessor,
IJsonDiffService diffService)
{
_tenantContext = tenantContext;
_httpContextAccessor = httpContextAccessor;
_diffService = diffService;
}
private List<AuditLog> CreateAuditEntries(DbContext context)
{
// ... existing code ...
foreach (var entry in entries)
{
string? oldValues = null;
string? newValues = null;
if (entry.State == EntityState.Modified)
{
// Use diff service to get only changed fields
var changes = _diffService.GetChangedFields(entry);
var diffJson = _diffService.SerializeChangedFields(changes);
// Store diff in both OldValues and NewValues
// OldValues: { "Title": { "OldValue": "Old", "NewValue": "New" } }
oldValues = diffJson;
newValues = diffJson;
}
else if (entry.State == EntityState.Added)
{
// For Create, store all current values
newValues = SerializeAllFields(entry);
}
else if (entry.State == EntityState.Deleted)
{
// For Delete, store all original values
oldValues = SerializeAllFields(entry);
}
var auditLog = new AuditLog
{
// ... existing fields ...
OldValues = oldValues,
NewValues = newValues
};
auditLogs.Add(auditLog);
}
return auditLogs;
}
private string SerializeAllFields(EntityEntry entry)
{
var allFields = entry.Properties
.Where(p => !p.Metadata.IsPrimaryKey())
.ToDictionary(p => p.Metadata.Name, p => p.CurrentValue);
return JsonSerializer.Serialize(allFields);
}
}
Example Output:
// Before (Full Snapshot - 500 bytes):
{
"OldValues": {"Id":"abc","Title":"Old Title","Description":"Long description...","Status":"InProgress","Priority":1},
"NewValues": {"Id":"abc","Title":"New Title","Description":"Long description...","Status":"InProgress","Priority":1}
}
// After (Diff Only - 80 bytes, 84% reduction):
{
"OldValues": {"Title":{"OldValue":"Old Title","NewValue":"New Title"}},
"NewValues": {"Title":{"OldValue":"Old Title","NewValue":"New Title"}}
}
Technical Notes
- Use System.Text.Json for performance
- Store diff in both OldValues and NewValues for query flexibility
- Consider nested object changes (e.g., Address.City)
- Ignore computed properties and navigation properties
Testing
Unit Tests:
[Fact]
public void GetChangedFields_ShouldReturnOnlyModifiedFields()
{
// Arrange
var entry = CreateMockEntry(
original: new { Title = "Old", Status = "InProgress" },
current: new { Title = "New", Status = "InProgress" }
);
// Act
var changes = _diffService.GetChangedFields(entry);
// Assert
Assert.Single(changes.Fields);
Assert.True(changes.Fields.ContainsKey("Title"));
Assert.Equal("Old", changes.Fields["Title"].OldValue);
Assert.Equal("New", changes.Fields["Title"].NewValue);
}
Created: 2025-11-05 by Backend Agent