6

Używam Simple Injector, ale może potrzebuję więcej konceptualnej odpowiedzi.W jaki sposób za pomocą wtyczki zależności uzyskać konfigurację z wielu źródeł?

Oto oferta, załóżmy mam interfejs z moich ustawień aplikacji:

public interface IApplicationSettings 
{ 
    bool EnableLogging { get; } 
    bool CopyLocal { get; } 
    string ServerName { get; } 
} 

Następnie należałoby zazwyczaj mają klasę, która implementuje IApplicationSettings, coraz każde pole z określonego źródła, na przykład:

public class AppConfigSettings : IApplicationSettings 
{ 
    private bool? enableLogging; 
    public bool EnableLogging 
    { 
     get 
     { 
      if (enableLogging == null) 
      { 
       enableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]; 
      } 
      return enableLogging; 
     } 
    } 
    ... 
} 

JEDNAK! Załóżmy, że chcę uzyskać EnableLogging z app.config, CopyLocal z bazy danych i ServerName z innej implementacji, która pobiera bieżącą nazwę komputera. Chcę móc łączyć ze sobą konfigurację mojej aplikacji bez konieczności tworzenia 9 implementacji, po jednej dla każdej kombinacji.

Zakładam, że nie mogę przekazać żadnych parametrów, ponieważ interfejsy są rozwiązywane przez wtryskiwacz (kontener).

myślałem o tym, początkowo:

public interface IApplicationSettings<TEnableLogging,TCopyLocal,TServerName> 
where TEnableLogging : IGetValue<bool> 
where TCopyLocal : IGetValue<bool> 
where TServerName : IGetValue<string> 
{ 
    TEnableLogging EnableLog{get;} 
    TCopyLocal CopyLocal{get;} 
    TServerName ServerName{get;} 
} 

public class ApplicationSettings<TEnableLogging,TCopyLocal,TServerName> 
{ 
    private bool? enableLogging; 
    public bool EnableLogging 
    { 
     get 
     { 
      if (enableLogging == null) 
      { 
       enableLogging = Container.GetInstance<TEnableLogging>().Value 
      } 
      return enableLogging; 
     } 
    } 
} 

Jednak z tego mam jeden główny problem: Skąd mam wiedzieć, jak utworzyć instancję TEnableLogging (co jest IGetValue<bool>)? Załóżmy, że IGetValue<bool> to interfejs, który ma właściwość Value, która zostanie zaimplementowana przez konkretną klasę. Ale konkretna klasa może potrzebować pewnych szczegółów (jak na przykład nazwa klucza w app.config) lub nie (może po prostu chcę wrócić zawsze prawdziwie).

Jestem stosunkowo nowy w zastrzyku uzależnienia, więc może myślę w niewłaściwy sposób. Czy ktoś ma jakieś pomysły, jak to osiągnąć?

(może odpowiedzieć używając innej biblioteki DI, nie będę nic. Chyba wystarczy chwycić pojęcie IT).

Odpowiedz

11

jesteś na pewno pozycją wspak tutaj.

Kilka lat temu zbudowałem aplikację, która zawiera interfejs podobny do twojego IApplicationSettings. Wydaje mi się, że nazwałem ją IApplicationConfiguration, ale zawiera ona również wszystkie wartości konfiguracyjne aplikacji.

Mimo że pomógł mi sprawić, że moja aplikacja zostanie przetestowana na początku, po jakimś czasie projekt zaczął wchodzić w drogę. Wiele implementacji zależało od tego interfejsu, ale ciągle się zmieniało, a wraz z nim implementacja i wersja testowa.

Tak jak ty, zaimplementowałem trochę leniwego ładowania, ale miało to fatalną wadę. Gdy brakowało jednej z wartości konfiguracyjnych, dowiedziałem się, że tak było, gdy wartość została wywołana po raz pierwszy. Spowodowało to konfigurację: hard to verify.

Zajęło mi kilka iteracji refaktoryzacji, co stanowi rdzeń problemu. Duże interfejsy są problemem. Moja klasa IApplicationConfiguration była niezgodna z Interface Segregation Principle, a wynikiem była słaba łatwość konserwacji.

W końcu dowiedziałem się, że ten interfejs był całkowicie bezużyteczny.Poza naruszeniem ISP, te wartości konfiguracyjne opisywały szczegóły implementacji i zamiast tworzyć szeroką abstrakcję aplikacji, znacznie lepiej było dostarczyć każdą implementację bezpośrednio z wartością konfiguracyjną, której potrzebowały.

Po wykonaniu tej czynności najłatwiej jest przekazać tę wartość konfiguracyjną jako wartość właściwości. Z Simple Injector można użyć metody RegisterInitializer dla tego:

var enableLogging = 
    Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]); 

container.RegisterInitializer<Logger>(logger => 
{ 
    logger.EnableLogging = enableLogging; 
}); 

Kiedy robi to wartość enableLogging odczytywane tylko raz z pliku konfiguracyjnego i odbywa się tak podczas uruchamiania aplikacji. To sprawia, że ​​jest szybka i sprawia, że ​​kończy się niepowodzeniem podczas uruchamiania aplikacji, gdy brakuje wartości.

Jeśli z jakiegoś powodu trzeba opóźnić czytania (z bazy danych na przykład), można użyć Lazy<T>:

Lazy<bool> copyLocal = new Lazy<bool>(() => 
    container.GetInstance<IDatabaseManager>().RunQuery(CopyLocalQuery)); 

container.RegisterInitializer<FileCopier>(copier => 
{ 
    copier.CopyLocal = copyLocal.Value; 
}); 

Zamiast przejściu wartości pomocą właściwości, można również użyć argumentów konstruktora, ale ten jest nieco trudniejsze do osiągnięcia. Wypróbuj look at this article dla niektórych pomysłów.

Jeśli rejestrujesz tę samą wartość konfiguracji dla wielu rejestracji, prawdopodobnie brakuje ci abstrakcji. Spójrz na to:

container.RegisterInitializer<UserRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<OrderRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<CustomerRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<DocumentRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 

w tym przypadku prawdopodobnie brakuje IDatabaseFactory lub IDatabaseManager abstrakcji, i należy zrobić coś takiego:

container.RegisterSingle<IDatabaseFactory>(new SqlDatabaseFactory(connectionString)); 
+1

Dziękuję, że popełnił wiele sensu! Ciekawe, że obaj podjęliśmy podobne kroki i zdaliśmy sobie sprawę, że coś idzie nie tak, jak powinno być haha –

Powiązane problemy