(+) Application Layer - DI

get_it νŒ¨ν‚€μ§€λ₯Ό μ΄μš©ν•΄μ„œ μ†μ‰½κ²Œ DI λ₯Ό κ΅¬ν˜„ν•  수 μžˆλ‹€. DI κ΅¬ν˜„μ— μžˆμ–΄μ„œ get_it νŒ¨ν‚€μ§€ 말고 λ‹€λ₯Έ νŒ¨ν‚€μ§€λ„ λ§Žμ΄λ“€ μ“°λŠ”μ§€ 잘 λͺ¨λ₯΄κ² λ‹€. 이게 κ°€μž₯ 보편적인 라이브러리둜 μ•Œκ³  μžˆλ‹€.

registerSingleton vs registerLazySingleton

final locator = GetIt.instance;

Future<void> init() async {
  locator.registerSingleton<T>(T instance);
  
  /// registers a type as Singleton by passing an [instance] of that type
  /// that will be returned on each call of [get] on that type
final locator = GetIt.instance;

Future<void> init() async {
  locator.registerLazySingleton<T>(FactoryFunc<T> func);
  
  /// registers a type as Singleton by passing a factory function that will be called
  /// on the first call of [get] on that type  

κ°•μ˜μ—μ„œλŠ” registerLazySingleton λ₯Ό μ‚¬μš©ν–ˆκ³ , 이 외에 λ‹€λ₯Έ 유투브 κ°•μ˜ μ—μ„œλ„ λ™μΌν•˜κ²Œ registerLazySingleton λ₯Ό μ‚¬μš©ν–ˆλ‹€.

registerLazySingleton λŠ” 말 κ·Έλž˜λ„ lazy ν•˜κ²Œ register ν•œλ‹€λŠ” 의미인데 μœ„ μ„€λͺ…에도 μ“°μ—¬μžˆλ“―μ΄ register ν•˜λŠ” λŒ€μƒ μžμ²΄κ°€ registerLazySingleton μ—μ„œλŠ” instance κ°€ μ•„λ‹ˆλΌ factory function 이닀. λΉ„μŠ·ν•œ 예둜 JPA μ—μ„œ ν”„λ‘μ‹œ 객체처럼 첫번째 call λ•Œ λΉ„λ‘œμ†Œ μ‹€μ œ instance κ°€ μƒμ„±λœλ‹€κ³  λ³Ό 수 μžˆλ‹€. μ•„λž˜λŠ” chat gpt μ—μ„œ 차이λ₯Ό λ¬Όμ—ˆμ„λ•Œ λ‚˜μ˜¨ μ„€λͺ…이닀.

get_it νŒ¨ν‚€μ§€λŠ” μ˜μ‘΄μ„± μ£Όμž…μ„ κ΅¬ν˜„ν•˜κΈ° μœ„ν•œ νŒ¨ν‚€μ§€ 쀑 ν•˜λ‚˜μž…λ‹ˆλ‹€. 이 νŒ¨ν‚€μ§€μ—μ„œλŠ” singletonκ³Ό lazySingleton 두 κ°€μ§€ μ’…λ₯˜μ˜ 등둝 방법을 μ œκ³΅ν•©λ‹ˆλ‹€.

GetIt.instance.registerSingleton<MyService>(MyService());

singleton은 처음 μ‚¬μš©λ  λ•Œ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜κ³ , μ΄ν›„μ—λŠ” 항상 λ™μΌν•œ μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜λŠ” 등둝 λ°©μ‹μž…λ‹ˆλ‹€. get_it νŒ¨ν‚€μ§€μ—μ„œ registerSingleton λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ 등둝할 수 μžˆμŠ΅λ‹ˆλ‹€.

GetIt.instance.registerLazySingleton<MyService>(() => MyService());

azySingleton은 처음 μ‚¬μš©λ  λ•Œ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜κ³ , μ΄ν›„μ—λŠ” 항상 λ™μΌν•œ μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜λŠ” 등둝 λ°©μ‹μž…λ‹ˆλ‹€. singleton과의 차이점은 처음 μ‚¬μš©λ˜κΈ° μ „κΉŒμ§€ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” μ μž…λ‹ˆλ‹€. get_it νŒ¨ν‚€μ§€μ—μ„œ registerLazySingleton λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ 등둝할 수 μžˆμŠ΅λ‹ˆλ‹€.

즉, singleton은 등둝 μ‹œμ μ— λ°”λ‘œ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜κ³ , lazySingleton은 μΈμŠ€ν„΄μŠ€κ°€ ν•„μš”ν•  λ•Œ μƒμ„±λ©λ‹ˆλ‹€. λ”°λΌμ„œ lazySingleton은 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ‹œμž‘λ  λ•Œ λ§Žμ€ λ©”λͺ¨λ¦¬λ₯Ό μ°¨μ§€ν•˜λŠ” 경우 μœ μš©ν•©λ‹ˆλ‹€. 일반적으둜 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ‚¬μš©ν•˜λŠ” λŒ€λΆ€λΆ„μ˜ μ„œλΉ„μŠ€λŠ” lazySingleton으둜 λ“±λ‘ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

κ°•μ˜λ‚΄ DI μ‚¬μš© 예제

κ°•μ˜μ—μ„œλŠ” μ•„λž˜μ™€ 같이 μ²˜λ¦¬ν•˜κ³  μžˆλ‹€.

final instance = GetIt.instance;

Future<void> initAppModule() async {
  final sharedPrefs = await SharedPreferences.getInstance();

  // shared prefs instance
  instance.registerLazySingleton<SharedPreferences>(() => sharedPrefs);

  // app prefs instance
  instance
      .registerLazySingleton<AppPreferences>(() => AppPreferences(instance()));

  // network info
  instance.registerLazySingleton<NetworkInfo>(
      () => NetworkInfoImpl(DataConnectionChecker()));

  // dio factory
  instance.registerLazySingleton<DioFactory>(() => DioFactory(instance()));

  // app  service client
  final dio = await instance<DioFactory>().getDio();
  instance.registerLazySingleton<AppServiceClient>(() => AppServiceClient(dio));

  // remote data source
  instance.registerLazySingleton<RemoteDataSource>(
      () => RemoteDataSourceImplementer(instance()));

  // local data source
  instance.registerLazySingleton<LocalDataSource>(
      () => LocalDataSourceImplementer());

  // repository
  instance.registerLazySingleton<Repository>(
      () => RepositoryImpl(instance(), instance(), instance()));
}

initLoginModule() {
  if (!GetIt.I.isRegistered<LoginUseCase>()) {
    instance.registerFactory<LoginUseCase>(() => LoginUseCase(instance()));
    instance.registerFactory<LoginViewModel>(() => LoginViewModel(instance()));
  }
}

initAppModule μ—μ„œ application layer 의 diλ₯Ό λͺ¨λ‘ μ²˜λ¦¬ν•΄μ£Όκ³ , 둜그인 view μ—μ„œ ν•„μš”ν•œ usecase, viewModel 은 initLoginModule μ—μ„œ λ”°λ‘œ μ²˜λ¦¬ν•΄μ€€λ‹€. 그리고 이λ₯Ό μ•„λž˜μ™€ 같이 resetModules μ—μ„œ ν•œλ²ˆμ— μ²˜λ¦¬ν•΄μ£Όλ©΄μ„œλ„ route μ—μ„œλ„ λ”°λ‘œ 또 λ„£μ–΄μ£Όκ³  μžˆλ‹€.

resetModules() {
  instance.reset(dispose: false);
  initAppModule();
  initHomeModule();
  initLoginModule();
  initRegisterModule();
  initForgotPasswordModule();
  initStoreDetailsModule();
}
  static Route<dynamic> getRoute(RouteSettings routeSettings) {
    switch (routeSettings.name) {
      case Routes.splashRoute:
        return MaterialPageRoute(builder: (_) => SplashView());
      case Routes.loginRoute:
        initLoginModule();
        return MaterialPageRoute(builder: (_) => LoginView());
      case Routes.onBoardingRoute:
        return MaterialPageRoute(builder: (_) => OnBoardingView());
      case Routes.registerRoute:
        initRegisterModule();
        return MaterialPageRoute(builder: (_) => RegisterView());
      case Routes.forgotPasswordRoute:
        initForgotPasswordModule();
        return MaterialPageRoute(builder: (_) => ForgotPasswordView());
      case Routes.mainRoute:
        initHomeModule();
        return MaterialPageRoute(builder: (_) => MainView());
      case Routes.storeDetailsRoute:
        initStoreDetailsModule();
        return MaterialPageRoute(builder: (_) => StoreDetailsView());
      default:
        return unDefinedRoute();
    }
  }

registerFactory

그리고 usecase, viewModel 의 di μ²˜λ¦¬μ™€ application layer μ—μ„œμ˜ di 처리 사이에 κ°€μž₯ 큰 차이라 ν•œλ‹€λ©΄ registerFactory() λ₯Ό μ‚¬μš©ν–ˆλ‹€λŠ” 것이닀.

  /// registers a type so that a new instance will be created on each call of [get] on that type
  /// [T] type to register
  /// [factoryFunc] factory function for this type
  /// [instanceName] if you provide a value here your factory gets registered with that
  /// name instead of a type. This should only be necessary if you need to register more
  /// than one instance of one type. Its highly not recommended
  void registerFactory<T extends Object>(
    FactoryFunc<T> factoryFunc, {
    String? instanceName,
  });

registers a type so that a new instance will be created on each call of [get] on that type μ„€λͺ…κ³Ό 같이 μ‚¬μš© 될 λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ 객체λ₯Ό λ°˜ν™˜ν•΄μ£Όλ„λ‘ ν•˜κΈ° μœ„ν•΄μ„œ Factory λ₯Ό λ“±λ‘ν•œλ‹€.

μ—¬κΈ°κΉŒμ§€κ°€ κ°•μ˜μ—μ„œ μ‚¬μš©λœ usecase, viewModel 에 λŒ€ν•œ κ΅¬ν˜„ 방식이닀. κ°•μ˜μ—μ„œ λͺ…ν™•ν•˜κ²Œ 이 뢀뢄을 μ™œ λ‹€λ₯΄κ²Œ κ΅¬ν˜„ν–ˆλŠ”κ°€λ₯Ό μ„€λͺ…을 ν•΄μ£Όμ§€ μ•Šκ³  μžˆλ‹€.

결과적으둜 usecase, viewModel 에 λŒ€ν•΄μ„œ registerLazySingleton κ³Ό registerFactory λ₯Ό μ‚¬μš©ν–ˆμ„λ•Œ 각각 μ–΄λ–»κ²Œ λ‹€λ₯΄κ²Œ 처리 λ˜λŠ”κ°€λ₯Ό 생각해보면 쒋을 것 κ°™λ‹€. 일단 registerLazySingleton 의 경우 계속 썼던 것을 μž¬ν™œμš©ν•˜κ²Œ λ˜λŠ” 것이고 registerFactory λ₯Ό μ‚¬μš©ν•˜λ©΄ μ“Έ λ•Œλ§ˆλ‹€ μƒˆλ‘œ λ§Œλ“€μ–΄μ„œ μ‚¬μš©ν•˜λŠ” 것이닀.

μ΄λ•Œ μƒˆλ‘œ λ§Œλ“€κ²Œ 되면 λ‹Ήμ—°νžˆ μ§€μ—­λ³€μˆ˜μ™€ 같은 객체의 μƒνƒœμžμ²΄κ°€ μ „λΆ€ μ΄ˆκΈ°ν™” λ˜μ–΄μ„œ 처음 μƒνƒœλ‘œ λŒμ•„κ°€λŠ” 것이고 μƒνƒœλ₯Ό μœ μ§€μ‹œν‚€κ³  μ‹Άλ‹€λ©΄ singleton 을 μ‚¬μš©ν•΄μ•Ό ν•  것이닀. viewModel μ—μ„œ νŠΉλ³„νžˆ μœ μ§€ν•΄μ•Όν•  μƒνƒœκ°€ μ—†κ³ , λ°›μ•„μ™€μ„œ λ³΄μ—¬μ£ΌλŠ” 데이터 λͺ¨λ‘ μ„œλ²„μ™€ λ™κΈ°ν™”λ‘œ λ™μž‘ν•˜λŠ” 것이라면 ꡳ이 μƒˆλ‘œ λ§Œλ“€ ν•„μš” 없이 singleton 을 μ‚¬μš©ν•˜λŠ” 것이 λ‚˜μ„ 것 κ°™λ‹€.

λ‚΄κ°€ κ΅¬ν˜„ν•œ DI μ‚¬μš© ν˜•νƒœ

아직 ν”„λ‘œμ νŠΈκ°€ μ™„μ„± 단계가 μ•„λ‹ˆκ³  초기 κ΅¬ν˜„ λ‹¨κ³„λΌμ„œ μ½”λ“œ 양이 λ§Žμ§€ μ•Šμ€λ° μ–΄λŠ 정도 틀이 μž‘ν˜€μžˆμ–΄μ„œ 일단 μ½”λ“œλ₯Ό 남긴닀. κ°•μ˜μ—μ„œλŠ” ν•„μš”ν•  λ•Œλ§ˆλ‹€ init μ‹œν‚¨ λ°˜λ©΄μ— λ‚˜λŠ” μ €λ ‡κ²Œ 일단 private method 둜 λΆ„λ₯˜λ₯Ό ν•΄λ‘” λ’€ ν•˜λ‚˜μ˜ λ©”μ†Œλ“œ(prepareDependencies()) 에 λͺ¨λ‘ 넣어두고 이λ₯Ό μ•± μ‹€ν–‰μ‹œ λ°”λ‘œ μ‹€ν–‰μ‹œν‚€λŠ” ν˜•νƒœλ‘œ κ΅¬ν˜„ν–ˆλ‹€.

class Injector {
  Injector._();

  static GetIt get _instance => GetIt.instance;

  static DeviceInfoPlugin get deviceInfoPlugin =>
      _instance.get<DeviceInfoPlugin>();

  static Future prepareDependencies() async {
    _prepareUtils();
    _prepareNetworks();
  }

  static void _prepareUtils() {
    _instance.registerLazySingleton<DeviceInfoPlugin>(() => DeviceInfoPlugin());
  }

  static void _prepareNetworks() {
    _instance.registerLazySingleton(
      () => TodoApiClient(
        clientBaseUrl: Environment.baseUrl,
        customInterceptors: [BaseHeaderInterceptor()],
      ),
    );
  }
}
void run() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Injector.prepareDependencies();
  runApp(const Application());
}

Last updated