기타
C#에서 SOLID 원칙을 활용한 Repository 패턴 구현 가이드
이영훈닷컴
2025. 5. 14. 09:25
728x90
오늘은 C# 개발자라면 꼭 알아야 할 'Repository 패턴'과 이를 SOLID 원칙에 기반해 구현하는 방법에 대해 알아보겠습니다. Repository 패턴은 데이터베이스 접근 코드와 비즈니스 로직을 분리해 코드의 가독성과 유지보수성을 높여주는 디자인 패턴입니다. 특히, SOLID 원칙을 준수하면 더욱 깔끔하고 확장 가능한 코드를 작성할 수 있습니다.
Repository 패턴의 장점
- 계층 분리: 데이터베이스 접근 로직을 비즈니스 로직과 분리합니다.
- 교체 용이성: Entity Framework에서 Dapper 등 다른 ORM으로 쉽게 전환할 수 있습니다.
- 단위 테스트 지원: Repository를 Mocking하여 테스트 환경을 손쉽게 구성할 수 있습니다.
- 데이터 관리 효율성: 데이터 접근 방식을 중앙 집중화하여 관리합니다.
SOLID 원칙과 Repository 패턴
SOLID 원칙을 따르면 Repository 패턴이 더욱 강력해집니다:
- 단일 책임 원칙(SRP): 각 Repository는 특정 엔터티의 데이터 접근만 담당해야 합니다.
- 개방-폐쇄 원칙(OCP): 기존 코드를 변경하지 않고 새로운 Repository나 쿼리를 추가할 수 있어야 합니다.
- 리스코프 치환 원칙(LSP): 하나의 Repository를 다른 것으로 대체해도 문제가 없어야 합니다.
- 인터페이스 분리 원칙(ISP): 여러 메서드가 포함된 거대한 인터페이스 대신, 목적에 맞는 작은 인터페이스를 사용하는 것이 좋습니다.
- 의존성 역전 원칙(DIP): Repository는 ORM(Entity Framework 등)에 직접 의존하지 않고 추상화를 통해 접근해야 합니다.
C#에서의 Repository 패턴 구현
1. 엔터티 정의
csharp
public class Customer {
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
2. Generic Repository 생성
Generic Repository를 사용하면 코드 재사용성을 극대화할 수 있습니다:
csharp
public interface IRepository where T : class {
Task GetByIdAsync(int id);
Task<IEnumerable> GetAllAsync();
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
}
3. Entity Framework를 활용한 구현
csharp
public class Repository : IRepository where T : class {
protected readonly AppDbContext _context;
protected readonly DbSet _dbSet;
public Repository(AppDbContext context) {
_context = context;
_dbSet = context.Set();
}
public async Task GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<IEnumerable> GetAllAsync() => await _dbSet.ToListAsync();
public async Task AddAsync(T entity) {
await _dbSet.AddAsync(entity);
await _context.SaveChangesAsync();
}
public async Task UpdateAsync(T entity) {
_dbSet.Update(entity);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id) {
var entity = await _dbSet.FindAsync(id);
if (entity != null) {
_dbSet.Remove(entity);
await _context.SaveChangesAsync();
}
}
}
4. 특화된 Repository 생성
특정 엔터티에 대한 특화된 기능은 별도 인터페이스와 클래스로 구현합니다:
csharp
public interface ICustomerRepository : IRepository {
Task GetByEmailAsync(string email);
}
public class CustomerRepository : Repository, ICustomerRepository {
public CustomerRepository(AppDbContext context) : base(context) { }
public async Task GetByEmailAsync(string email) {
return await _context.Customers.FirstOrDefaultAsync(c => c.Email == email);
}
}
Best Practice: 작은 Repository로 유지보수성을 높이자
- 문제점: 거대한 Repository는 유지보수가 어렵고, ISP(인터페이스 분리 원칙)를 위반할 가능성이 큽니다.
- 해결책: Generic Repository는 기본 CRUD 작업에만 사용하고, 특화된 Repository는 복잡한 쿼리나 비즈니스 로직에 활용하세요.
결론
Repository 패턴은 데이터 접근 코드를 구조화하고 유지보수를 간소화하는 데 매우 유용합니다. 여기에 SOLID 원칙을 적용하면 더욱 깔끔하고 유연한 코드를 작성할 수 있습니다. 이제 여러분의 프로젝트에 Repository 패턴을 도입해 보세요!