Czy uzywajac kontenera nie powinno sie nigdzie dawac new?

0

Czy jak w projekcie uzyty jest ninject lub autofac to nigdzie nie powinno sie uzywac new?

Trafiłem w jakimś kursie na taką linijkę (pomimo używania kontenera):

var client = new HttpClient();
clien.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
5

Kontener powinien być wykorzystywany do pobierania zależności (serwisów) - wynika to stąd, że w Twoich serwisach powinieneś opierać się na interfejsach, których konkretne implementacje powinien znać / łączyć dopiero kontener (na tym polega cała idea DI).

Za pomocą new powinieneś tworzyć DTO oraz inne tego typu obiekty, których wybór / zachowanie / itd. nie różni się w runtime'ie (takie obiekty nie są formalnie w ogóle zależnościami Twojego serwisu).

Opierając się na Twoim przykładzie: najprawdopodobniej HttpClient powinieneś pobierać z kontenera, podczas gdy MediaTypeWithQualityHeaderValue zostało prawidłowo utworzone za pomocą new.

3

To zalezy gdzie tworzy sie nowy obiekt- kontekst jest wszystkim. Ciezko odpowiedziec bez tego. Ogolnie tam gdzie sie da najlepiej stosowac Dependency Injection i wykorzystywac kontener do tworzenia obiektow. Natomiast w tym konkretnym przypadku ktory podales z HttpClient to nie powinno sie tworzyc nowej instacji za kazdy razem kiedy chcemy wykonac request pod ten sam adres.

HttpClient is intended to be instantiated once and reused throughout the life of an application. The following conditions can result in SocketException errors:
Creating a new HttpClient instance per request.
Server under heavy load.
Creating a new HttpClient instance per request can exhaust the available sockets.

Zrodlo

0

@Aventus: czyli np wydzielic HttpClient do osobnej klasy i zrobic go jako statycznego np:

public static class ClientHelper
{
  public static HttpClient Client {get; private set;}
  public static void Init()
  {
    Client = new HttpClient();
    Clien.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  }
}
2

Jeśli masz możliwość wstrzykiwać zależności to ja bym stworzył dedykowaną klasę (np. MyRequestService) dla konkretnego requestu. Ta klasa wiedziała by po jakim kluczu wyciągnąć potrzebny URL np. z konfiguracji. Następnie wyabstrahował bym to do interfejsu (w celu podmienienia tego w testach) i zarejestrował tą implementację wraz z interfejsem w kontenerze, jako singleton. Wtedy kontener zajmie się prawidłowym zarządzaniem "życia" tego obiektu.

3

Wtrącę swoje dwa grosze, chociaż lekko dywaguję :]

Kontener powinien być wykorzystywany do pobierania zależności (serwisów) - wynika to stąd, że w Twoich serwisach powinieneś opierać się na interfejsach, których konkretne implementacje powinien znać / łączyć dopiero kontener (na tym polega cała idea DI).
Za pomocą new powinieneś tworzyć DTO oraz inne tego typu obiekty, których wybór / zachowanie / itd. nie różni się w runtime'ie (takie obiekty nie są formalnie w ogóle zależnościami Twojego serwisu).

Wstrzykiwanie zależności można łączyć z polimorfizmem, ale samo w sobie wstrzykiwanie nie ma z polimorfizmem nic wspólnego.

In software engineering, dependency injection is a technique whereby one object (or static method) supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client's state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
The intent behind dependency injection is to decouple objects to the extent that no client code has to be changed simply because an object it depends on needs to be changed to a different one. This permits following the Open / Closed principle.

changed to a different one możne oznaczać, że np raz wstrzykujemy stringa 'kasia', a raz 'basia', albo raz wstrzykujemy HttpClienta łączącego się do klastra A, a raz do klastra B.

Robienie interfejsów z jedną produkcyjną implementacją to już w ogóle moim zdaniem bezsens (YAGNI), ale mam przeczucie, że w C# jest dość popularne.

0

Przykład z mojego podwórka:
Stwórz sobie fabrykę abstrakcyjną Clienta Http.
I ją możesz sobie wstrzykiwać, a nie będzie Cię kłuło w oczy, że masz gdzieś 'new'

0

@Wibowit:

Robienie interfejsów z jedną produkcyjną implementacją to już w ogóle moim zdaniem bezsens (YAGNI), ale mam przeczucie, że w C# jest dość popularne.

Ale to chyba nie było odniesienie do tego co pisałem?

1

Najlepiej to by było zacząć od podstaw i opowiedzieć na pytanie po co w ogóle wstrzykujemy? co chcemy przez to osiągnąć? przecież nie robimy tego bo tak :D

  • wstrzykujemy po to by móc podmienić implementację wstrzykiwanego typu bez modyfikacji klasy do której wstrzykujemy
  • a potrzebujemy podmieniać implementację przede wszystkim na potrzeby testów by móc testować daną klasę

Więc jak piszemy test dla danej klasy to od razu mamy odpowiedź czy potrzebujemy by dana zależność była wstrzykiwana czy nie.
A jak nie piszemy testu, to zastanówmy się jakbyśmy testowali daną klasę, to czy byśmy chcieli testować w izolacji od danej zależności czy nie, jeśli w izolacji to mamy kandydata na wstrzyknięcie, jeśli nie to możemy użyć "new".

0

Robienie interfejsów z jedną produkcyjną implementacją to już w ogóle moim zdaniem bezsens (YAGNI), ale mam przeczucie, że w C# jest dość popularne.

bo to nie java i inaczej wszystko trzeba by było oznaczyć jako virtual żeby to zamockować do unit testów, albo używać narzędzi które mogą co prawda pozwalać na zmianę implementacji nawet statycznych klas, ale operują na ILCode bezpośrednio, poniżej poziomu języka i trzeba zaufać jakiejś magii co jest czasem nieintuicyjne; łatwo też gdzieś zgubić konfigurację i dopuścić faktyczną implementację do testów, potem nagle po kilku miesiącach ktoś sprawdza czemu testy się wolno wykonują i okazuje się że lecą zapytania do rzeczywistej bazy danych albo dysku

też nie po to domyślnie metody i własności nie są wirtualne żeby je spowalniać do poziomu javy, a posiadanie interfejsu ma zazwyczaj swoje korzyści w przyszłości kiedy nagle chcemy część kodu wydzielić do biblioteki i nagle potrzebujemy drugiej implementacji - poza tym to dosłownie dwa kliknięcia - extract interface -> ok; można trzymać na początku w tym samym pliku

1

Niektórzy pewnie będą smutni, że dygresja się rozwija, ale nadarzyła się świetna okazja do wojenki Java vs C# :]

łatwo też gdzieś zgubić konfigurację i dopuścić faktyczną implementację do testów, potem nagle po kilku miesiącach ktoś sprawdza czemu testy się wolno wykonują i okazuje się że lecą zapytania do rzeczywistej bazy danych

Przypadkowo zdarzyło ci się skonfigurować bazę w testach? Zainteresuj się wstrzykiwaniem zależności - nie wstrzykniesz konfiguracji, to jej nie będzie :)

też nie po to domyślnie metody i własności nie są wirtualne żeby je spowalniać do poziomu javy

Masz bardzo kiepskie pojęcie na temat JVMa i dlatego opowiedziałeś bajkę oderwaną od rzeczywistości. To w C# usuwa się virtual z metod, po to by działały tak szybko jak w Javie. Wirtualność metod w Javie ma praktycznie zerowy wpływ na wydajność dopóki tych metod nie nadpisujesz (a bez wirtualności i tak być ich nie nadpisał). Nie ma wydajnościowej różnicy między używaniem getterów i setterów (to są wirtualne metody!) w Javie a używaniem pól bezpośrednio.

Co więcej Java już w 2011 roku potrafiła jednocześnie:

  • zdewirtualizować metodę
  • wkleić ją (inlining)
  • przenieść alokację obiektu alokowanego przez new na stos
  • przeprowadzić dalsze uproszczenia (w tym np kolejne dewirtualizacje metod na obiekcie przeniesionym na stos)

Przykład: C# i .NET Dlaczego foreach jest gorsze od for (beta)

a posiadanie interfejsu ma zazwyczaj swoje korzyści

Takie jak metody wirtualne :) To, że nie ma jawnego słówka virtual w C# nie znaczy, że metoda nie jest wirtualna. Podobnie w Javie nie ma jawnego słówka virtual, ale czasem też nie ma jawnego słówka public, abstract, final czy innych. Kompilator sobie wstawia co trzeba.

Z jakiegoś niewiadomego powodu też wywoływanie wirtualnych metod w interfejsów jest w C# wolniejsze od wywoływania metod explicite oznaczonych jako virtual i to nawet na .NET Core 2.1. Moje benchmarki tutaj: C# i .NET Mockowanie - interfejs vs virtual

poza tym to dosłownie dwa kliknięcia - extract interface -> ok; można trzymać na początku w tym samym pliku

Nooo, generowanie getterów i setterów w Javie to też dwa kliknięcia - tylko po co się skazywać na przebijanie się w kółko przez ten boilerplate?

0

Kontener jest do rozwiązywania problemów z zależnościami pomiędzy paczkami

Albo do ich przykrywania (i w efekcie powodowania dalszych problemów tego typu). Problemy z tight coupling można rozwiązać przez refaktor tak by mieć loose coupling, ale można też zaprząc do pracy kontener. Kontener radzi sobie bez problemu z najbardziej szalonymi grafami zależności między obiektami (włącznie z cyklami "rozwiązywanymi" przez proxy, setter injection, etc) i wprowadza iluzję iż dopóki kontener wstaje to nie ma problemów z zależnościami. Inaczej mówiąc: kontener sprawia iż pakowanie się w tight coupling jest, przynajmniej z początku, znacznie przyjemniejsze niż pozbywanie się go.

1

W uzupełnieniu tekstu @Wibowit - w javie już od jakiegoś czasu część osób walczy z kupą powodowaną przez wstrzykiwanie przez kontenery. (Edit: była poprawka zdania)
Przykładowe video Tomera Gabela:

I jest tam ładne podsumowanie If You are seeing benefit from IoC your codebase is already out of control.
(IoC w znaczneiu kontener DI).

1

@jarekr000000:

(...)już od jakiegoś czasu część osób walczy z kupą powodowaną przez kontenery i wstrzykiwanie.

Ale konkretnie to z jaką kupą? Ok, jeśli ktoś robi sobie miszmasz zależności to faktycznie DI i kontenery mogą to zamaskować. Nie widzę tylko co w tym złego robi sama idea DI. Tak samo zacytowane przez Ciebie zdanie (domyślam się że wyrwane z kontekstu, więc może to był błąd) można by podpiąć pod setki innych zagadnień. "Jeśli widzisz zysk z X to Twój kod już jest poza kontrolą". Tak naprawdę nic merytorycznego to nie wnosi.

Jeśli mam zależność, która z kolei posiada również jakąś zależność to chyba lepiej oddelegować odpowiedzialność budowania tejże zależności gdzieś indziej zamiast zatruwać kod który ma jedynie z tego korzystać, a nie martwić się jak to zbudować. A jeśli zależność ta nie może być wykonana podczas testów (ponieważ np. wykonuje requesty HTTP) to raczej nie wykorzystam fabryki bezpośrednio tylko właśnie użyje DI.

Nie wiem, będę musiał chyba oglądnąć podany przez Ciebie filmik jak znajdę trochę czasu, bo jak na razie brzmi to jak szukanie problemów na siłę.

2

Jeszcze dodam jedną ważną kwestię odnośnie DI przez konstruktor- pozwala to również jasno zdefiniować kontrakt danej klasy, oraz zależności bez których ta klasa nie będzie mogła poprawnie wykonać swoich odpowiedzialności. Bez zapewnienia tych zależności wiemy już na etapie budowania instancji że ma niespełnione wymagania.

Odnoszę wrażenie że podany wyżej przykłady próbują rozwiązać jakieś problemy w świecie Javy (mogę się mylić, nie mam zamiaru wdawać się w wojenki Java/C#), a próbuje się to przedstawić jako coś uniwersalnego i przenieść na inne ekosystemy gdzie takie problemy nie występują.

0

Odnoszę wrażenie że podany wyżej przykłady próbują rozwiązać jakieś problemy w świecie Javy (mogę się mylić, nie mam zamiaru wdawać się w wojenki Java/C#), a próbuje się to przedstawić jako coś uniwersalnego i przenieść na inne ekosystemy gdzie takie problemy nie występują.

Jeśli są w tym wątku przykłady w Javie w filmie podanym przez @jarekr000000 . Jednak w tym filmie gość wprost powiedział, że używa Javy bo jest najbardziej przystępna dla ogółu składniowo (jego zdaniem Java jest łatwiejsza do zrozumienia przez nie-Javowców niż analogicznie w przypadku innych języków). Powiedział też, że mechanizmy, które krytykuje występują między innymi w Javie (jak przykład podał Springa czy Guice), C# (tam też są kontenery DI, ale nie siedzi w C#, więc nie podał przykładów) oraz JavaScripcie (jako przykład podał Angulara, który zawiera kontener DI).

1
Aventus napisał(a):

Odnoszę wrażenie że podany wyżej przykłady próbują rozwiązać jakieś problemy w świecie Javy (mogę się mylić, nie mam zamiaru wdawać się w wojenki Java/C#), a próbuje się to przedstawić jako coś uniwersalnego i przenieść na inne ekosystemy gdzie takie problemy nie występują.

Zwykłe demonizowanie wroga nieznanego. Akurat Java i C# tu się raczej nie różnią. Widzę to nawet w tym wątku - te same argumenty.

Wystarczy dodać, że w dużo bardziej (od C#) składniowo rozbudowanej Scali, powoli ludzie też odwracają sięo d wszelkich cudownych paternów DI, które były o wiele mniej magiczne niż oparte na refleksji.
Po prostu powrót do kompilatora i new. Dlaczego?

Powód fajności kontenerów DI, możesz sobie wszystko wstrzyknąc gdzie chcesz, przez ileś warstw to powód chaosu.
Mi przypomina się analogiczna walka z GOTO (lata 80-te, początek 90). Argumentem za GOTO było to, że mozesz sobie skoczyć gdzie chcesz nawet do środka procedury i przez to zaoszczedzisz dużo kodowania, rozbijania na funkcje. I to prawda, tylko zaoszczędzona ilość lini kodu jest przy dobrym programowaniu mała, a kupa w kodzie duża.
Robiąc DI ręcznie, przy pomocy new muszę stworzyć więcej klas pośrednich i rozudowując system dostajęból kawałkami i po kawałku rozwiązuje (tworząc fabryki itp.). Przez to zwykle żadna moja klasa nie ma więcej niż kilka zależności (np. 3). Jak gdzieś mi się robi 5 to już wiem, że trzeba refaktorować.

Przy kontenerze DI, nawet z konstruktorami łatwo się rozszaleć i kończymy z klasami mającymi tych zależności 10. (A przy field injection to i 20).

Przy okazji jeszcze gorsze od kontenerowego DI (i z nim powiązane) są aspekty runtime i tego w Javie (Spring/ Java EE) jest pełno. Kontener automatycznie rozpoczynający transakcje w metodach, kontener sprawdzający uprawnienia na wejściu do metody itp. To jest dopiero niezła recepta na katastrofę na produkcji, ale dziesiątki tysiący serwisów tak jest robionych :-(

1

Ja korzystam z kontenerów IoC i nie zauważyłem więcej niż jakies 2-3 zależności na jedną klase. Faktycznie Spring działa przez refleksje, ale on wybuchnie na początku urcuhamiania aplikacji jeśli coś jest nie tak więc to średnio trafiony argument, nie widziałem żeby nagle aplikacja po 2 tygodniach od uruchomienia nagle padła przez kontener IoC. Aczkolwiek AOP rzeczywiście moża wywołac spore problemy...

0

Ja mam doświadczenie w c# - co prawda nie z ninjectem, ani autofac tylko z DI dostępnym by default w ASP .NET Core, oraz javą i springiem.

W C# założenia zespołu były takie:

  • minimalizacja "new" - chyba że to jakieś DTO
  • wstrzykiwanie tylko przez konstruktor
  • ASP.NET Core był na sterydach = większość opierała się o singletony, choć zazwyczaj używa się transientów(nowa instancja za każdym razem kiedy prosisz kontener)

Ogólnie nie działaliśmy zgodnie z założeniami Microsoftu, były problemy z Entity Framework itd. Choć nic czego nie dałoby się ogarnąć jakoś sensownie.
Aha no i ten kontener miał bardzo mało magii - nie było jakichś proxy itd - jeśli do singletonu wstrzyknięto transient, albo obiekt requestScope, to on tam po prostu zostawał. Co było upierdliwe, ale fajne. Ogólnie DI mi nie przeszkadzało i wszystko było w miarę łatwe, choć czasami ciężko było przewidzieć mi działanie kodu.

Natomiast w Springowych projektach było różnie:

  • Wstrzykiwanie przez konstruktor, ale i bardzo często do prywatnych pól.
  • Do singletonu można wstrzyknąć obiekt o requestScope i to działa - są proxy które to ogarniają automagicznie

W springu nie widzisz co skąd i dlaczego, "ify" na zależnościach to wiedza tajemna zespołu - a przynajmniej ja tak to widzę. W przeciwieństwie do ASP .NET Core gdzie definiuje się te zależności kodem

    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();

co jest dość przejrzyste i łatwo tym sterować w przeciwieństwie do springa, jego conditionali itd. Dotatkowo w springu używa się mnóstwa adnotacji(atrybutów) co zaśmieca kod i czyni go wg. mnie okropnym.

Co do pytania - to uważam że jeśli używasz kontenera to powinieneś go używać i minimalizować new. W każdym razie zastanowiłbym się czy faktycznie tego potrzebujesz.

A jeśli chodzi o Scale - to z tego co wiem że ludzie różnie sobie radzili i chyba wyszło im faktycznie na to że DI(takie frameworkowe) jest bez sensu. Chociaż słyszałem o jakichś lewych patternach, ale uznanych za antypatterny więc niegodne naśladowania.

EDIT: Pomimo tego uważam że Java jest fajna. a C# jest od Microsoftu i jest fajny, ale Microsoft nie jest fajny :P

0

@orchowskia:

ASP.NET Core był na sterydach = większość opierała się o singletony, choć zazwyczaj używa się transientów(nowa instancja za każdym razem kiedy prosisz kontener)

W Core zazwyczaj używa się scoped, chyba że są konkretne powody by tego nie robić.

0
WywaloneJajca napisał(a):

Problem w Javie jest taki, że po prostu macie słabe kontenery IOC, które łamią Open Closed, ponieważ trzeba rejestrować wszystkie beany ręcznie.

Nie wiem skąd te mądrości bierzesz , ale jak już czymś rzucasz to warto by doczytać.
W javie (s Wpringu) własnie nigdzie się tych beanów nie rejestruje tylko kontener sam je wyszukuje w kodzie (skanowanie klas) i to jest właśnie dramat ( BASIC).

Aventus napisał(a):

W Core zazwyczaj używa się scoped, chyba że są konkretne powody by tego nie robić.

To dziwne, bo z opisu wynika, że to odpowiednik springowych RequestScoped, SessionScoped czy JobScoped. W springu dzięki takim scope różne łazęgi przekazują cichaczem parametry między warstwami. Weź to potem refaktoruj jak parametr wygląda na niewykorzystany już w warstwie kontrolerów. A potem gdzieś w perzystencji ktoś go na nowo odkrywa :/ Fakt, że w springowcy chyba zrozumieli, że tak się nie robi i dawno mnie scope nie zaskoczył.

Edit:
Byłbym zapomniał. Żeby mieć luksus nie korzystania z kontenera IoC to trzeba mieć odpowiednią infrastrukturę. Jeśli zrobienie serwera web wymaga skanowania klas, które są potem podnoszone przy użyciu domyślnego konstruktora i jakoś zarządzane przez kontener to zima. Dawniej tak w javie było (servlety). Dopiero od kiedy można odpalić server z metody main to jest OK. Na pierwszy rzut oka taki Kestrel w .NET powinien dawać radę.

1

W springu dzięki takim scope różne łazęgi przekazują cichaczem parametry między warstwami

To nieźle. Ja z czymś takim się nie spotkałem, a nawet gdyby ktoś tak robił w .Net to świadczy o programiście. Nijak to się ma do DI, kontenerów czy czegokolwiek innego.

1 użytkowników online, w tym zalogowanych: 0, gości: 1