Dry Principle · How it works
1 min readRapid overview
How it works
_TODO: explain the mental model._
Practical C# Examples
Q: Show a full before/after refactoring that applies DRY.
A: Here is a service with duplicated validation, audit logging, and notification extracted into focused collaborators.
// BEFORE -- WET: validation, audit, and email duplicated in both methods
public class CustomerService
{
public async Task Create(CustomerDto dto)
{
if (string.IsNullOrWhiteSpace(dto.Name)) throw new ValidationException("Name required");
if (!dto.Email.Contains('@')) throw new ValidationException("Invalid email");
var c = new Customer { Name = dto.Name, Email = dto.Email };
_db.Customers.Add(c);
await _db.SaveChangesAsync();
_db.AuditLogs.Add(new AuditLog { Action = "Create", EntityId = c.Id });
await _db.SaveChangesAsync();
await _email.SendAsync(dto.Email, "Welcome!", $"Hello {dto.Name}");
}
public async Task Update(int id, CustomerDto dto)
{
if (string.IsNullOrWhiteSpace(dto.Name)) throw new ValidationException("Name required");
if (!dto.Email.Contains('@')) throw new ValidationException("Invalid email");
var c = await _db.Customers.FindAsync(id) ?? throw new NotFoundException();
c.Name = dto.Name; c.Email = dto.Email;
await _db.SaveChangesAsync();
_db.AuditLogs.Add(new AuditLog { Action = "Update", EntityId = c.Id });
await _db.SaveChangesAsync();
await _email.SendAsync(dto.Email, "Updated", $"Hello {dto.Name}");
}
}
// AFTER -- DRY: each concern extracted once
public class CustomerDtoValidator : AbstractValidator<CustomerDto>
{
public CustomerDtoValidator()
{
RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.Email).NotEmpty().EmailAddress();
}
}
public interface IAuditService { Task LogAsync(string action, string entity, int id); }
public class CustomerService(
AppDbContext db, IValidator<CustomerDto> validator,
IAuditService audit, IEmailSender email)
{
public async Task Create(CustomerDto dto)
{
validator.ValidateAndThrow(dto);
var c = new Customer { Name = dto.Name, Email = dto.Email };
db.Customers.Add(c);
await db.SaveChangesAsync();
await audit.LogAsync("Create", "Customer", c.Id);
await email.SendAsync(dto.Email, "Welcome!", $"Hello {dto.Name}");
}
public async Task Update(int id, CustomerDto dto)
{
validator.ValidateAndThrow(dto);
var c = await db.Customers.FindAsync(id) ?? throw new NotFoundException();
c.Name = dto.Name; c.Email = dto.Email;
await db.SaveChangesAsync();
await audit.LogAsync("Update", "Customer", c.Id);
await email.SendAsync(dto.Email, "Updated", $"Hello {dto.Name}");
}
}