Dry Principle · How it works

1 min read
Mid-level6 min read
Rapid 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}");
    }
}

See also