Interface와 DI
1. Interface
IUserRepository를 구현하는 클래스는 GetUser(int id) 메서드를 제공해야 한다는 규약
1 | public interface IUserRepository |
실제 구현
1 | /* |
1.1 Interface를 사용하는 이유
이러한 서비스가 있다고 가정을 하는 경우
1 | public class UserService |
이렇게 하면 UserService가 UserRepository라는 구체적인 클래스에 강하게 묶여버림
예로 테스트를 위해 UserRepository 대신 TestUserRepository를 사용하려면 UserService 내부의 의존성을 직접 수정해야 한다.
1 | public class UserService |
이제 UserService는 IUserRepository 기능만 확인한다
실제 객체가 UserRepository인지 아닌지는 관심 없음
2. DI
필요한 의존성을 밖에서 넣어준다.
1 | var repository = new UserRepository(); |
2.1 ASP.NET Core
매번 위처럼 직접 주입하지 않고 DI Container에 등록 하면 된다
1 | builder.Services.AddScoped<IUserRepository, UserRepository>(); |
누군가 IUserRepository를 필요로 하면 UserRepository를 만들어서 넣어줌
2.2 생명주기
2.2.1 Transient
1 | services.AddTransient<IUserRepository, UserRepository>(); |
사용할 때마다 새로운 객체를 만들어줌, 상태를 가지지 않는 가벼운 객체에 많이 사용
1 | Service A → UserRepository #1 |
둘이 같은 IUserRepository를 주입받아도 서로 다른 객체
2.2.2 Scoped
1 | services.AddScoped<IUserRepository, UserRepository>(); |
ASP.NET Core에서는 일반적으로 HTTP Request 하나 = Scope 하나로 생각하면 됨
예를 들어 요청이 2개 들어오면
1 | Request #1 |
Request #1 안에서는 같은 객체를 사용함
1 | Request #1 |
둘 다 IUserRepository를 요청하면 같은 객체를 받음
2.2.3 Singleton
1 | services.AddSingleton<IUserRepository, UserRepository>(); |
앱 전체에서 하나의 인스턴스를 공유 (기본적으로 필요할 때 생성)
1 | Application |
2.3 장점
2.3.1 구현체 변경이 쉬워진다
위에서 얘기한 UserRepository, TestUserRepository를 변경하려면
1 | // 실제 서버 |
만 바꿔주면 코드 수정을 할 필요가 없어진다
2.3.2 객체 생성/관리를 한 곳에서 한다
DI로 관리할 서비스 의존성을 개발자가 직접 생성할 필요 없이
DI 컨테이너가 객체 생성과 Lifetime을 관리함
2.4 조심해야할점
2.4.1 의존성이 너무 많아질 수 있음
1 | public UserService( |
갯수와 상관없이 클래스가 이 의존성들을 필요로 하는 것이 해당 클래스의 책임과 자연스럽게 연결되는가?를 생각해봐야함..
2.4.2 DI Container 설정을 잘못하면 런타임 문제가 생김
1 | services.AddSingleton<CacheService>(); |
CacheService는 애플리케이션이 살아있는 동안 하나인데, AppDbContext는 HTTP Request마다 새로 만들어지고 Request가 끝나면 폐기되는 객체임
Singleton인 CacheService가 Scoped인 AppDbContext를 의존하게 되면, 수명이 긴 객체가 수명이 짧은 객체를 참조하는 구조가 되어버림
잘못된 Lifetime 조합이며 ASP.NET Core DI 검증에서 예외가 발생할 수 있음