Kiedy pozwolić zarządzać serwisami dla IoC a kiedy tworzyć je przez new?

0

Stworzyłem klasę jak VerifierService i wydzieliłem ją do osobnego pakietu - ta klasa ma być taką fasadą do innych klas sprawdzających jakieś dane. Jest tam trochę zależności gdzie jedna klasa korzysta z kilku innych.
Tak to wygląda w strukturze pakietów

  - verifiers 
       - (public) `VerifierService` 
       - (package private) RegistrationFormVerifier...
       - (package private) abcVerifier....
        ....

O ile w niektórych miejscach wygodnie mi jest ten VerifierService po prostu wstrzyknąć, tak w innych miejscach jest to niemożliwe bo część klas, która potrzebuje tej zależności została utworzona przez new i nie jest zarządzana przez kontener. Zacząłem się zastanawiać co schrzaniłem w architekturze.

  1. Może ten serwis nie jest mi w ogóle potrzebny? Jeśli każda klasa byłaby publiczna to nie byłoby problemu, żeby albo coś wstrzyknąć albo stworzyć przez new?
  2. Nie wiem skąd mi się wzięło przekonanie, że serwisy z innych modułów powinny być wstrzykiwane przez IoC. Brędzę?
  3. Czy jest jakaś złota zasada, którą powinienem się kierować decydując czy dany serwis ma być wstrzykiwany przez IoC czy tworzony przez new?
    .
3

Możesz tworzyć wszystkie serwisy/singletony przez new w @Configuration - wtedy możesz mieć zero zależności do Springa w swoich klasach.

Jeśli chodzi o samą potrzebę, sprawdź czy nie naruszasz dependency inversion principle. Ewentualnie czy nie robisz jakichś dziwnych rzeczy w nieodpowiedniej warstwie, np. wołasz serwisy w encjach. Ale może masz wszystko ok, nie wszystkie klasy trzeba rejestrować w IoC.

0
Charles_Ray napisał(a):

Możesz tworzyć wszystkie serwisy/singletony przez new w @Configuration - wtedy możesz mieć zero zależności do Springa w swoich klasach.

Jeśli chodzi o samą potrzebę, sprawdź czy nie naruszasz dependency inversion principle. Ewentualnie czy nie robisz jakichś dziwnych rzeczy w nieodpowiedniej warstwie, np. wołasz serwisy w encjach. Ale może masz wszystko ok, nie wszystkie klasy trzeba rejestrować w IoC.

Pozwól, że dopytam bo mam wrażenie, że źle zrozumiałem i coś mi tutaj śmierdzi: to znaczy, że dla każdego podobnego pakietu, który będzie miał jedną klasę publiczną z wieloma ukrytymi zależnościami, musiałbym stworzyć osobną klasę konfiguracyjną i później w innym dowolnym miejscu wołać

ApplicationContext context = new AnnotationConfigApplicationContext(NazwaKlasyKonfiguracyjnejModułu.class);
MojBean bean = context.getBean("beanKtoregoPotrzebuje")

tak?

1

Każdy moduł może mieć oddzielna konfigurację, która rejestruje w IoC tylko publiczną fasadę. Resztę obiektów tworzysz przez new i nie wystawiasz na świat. Inaczej musiałbyś mieć wszystko publiczne, bo tak działają pakiety w Javie.

Co do użycia - przecież to co napisałeś robi się automatycznie i wystarczy, że w konstruktorze wskażesz potrzebna zależność (nie musisz nawet pisać @Autowired).

3

Mam tu prostą regułę.
Kiedy używać kontenera IoC? -> Nigdy.
Kiedy tworzyć obiekty przez new -> W pozostałych przypadkach.

2

Nie do końca widzę problem. Nie wiem czy wiesz, ale obiekty zarządzane przez kontener można przekazywać jako argumenty... Zacznij od zrobienia klasy @Configuration z definicja twoich singletonów i wywal z kodu wszystkie @Service, @Inject i @Autowired. Następnie zauważ że nic nie stoi na przeszkodzie żeby zrobić new Cośtam(beanPrzekazanyDoTegoSingletona)

0
Shalom napisał(a):

Nie do końca widzę problem. Nie wiem czy wiesz, ale obiekty zarządzane przez kontener można przekazywać jako argumenty... Zacznij od zrobienia klasy @Configuration z definicja twoich singletonów i wywal z kodu wszystkie @Service, @Inject i @Autowired. Następnie zauważ że nic nie stoi na przeszkodzie żeby zrobić new Cośtam(beanPrzekazanyDoTegoSingletona)

No dobrze: mam już tą klasę z @Configuration w której tworzę sobie beany. Istnieje jakiś serwis w którym te beany wstrzykuje i wszystko jest w porządku - śmiga. Jedna z metod w tym serwisie tworzy sobie jakąś tam strategię - przesyłam do innej klasy (stworzonej przez new) jakieś parametry wejściowe i w wyniku dostaję konkretną implementację (również stworzonej przez new) jakiegoś tam interfejsu. No i teraz w środku tej metody w serwisie wołam na tej implementacji metodę zrobCosTam(). W środku zrobCosTam() potrzebowałbym dostępu jednego z Beana, którego mam zdefiniowanego w klasie z @Configuration. Cały czas nie rozumiem jak mógłbym to zrobić.

Przyszły mi do głowy 3 opcje:

  1. Klasa, która dostarcza konkretnej strategii powinna przyjmować w konstruktorze parametr z tym beanem. Wtedy zamiast czegoś takiego:
SomeStrategy strategySupplier = new SomeStrategy();
StrategyImplementation strategyImplementation = strategySupplier.get(inputData);
strategyImplementation.zrobCosTam();

miałbym

SomeStrategy strategySupplier = new SomeStrategy(bean);
StrategyImplementation strategyImplementation = strategySupplier.get(inputData);
strategyImplementation.zrobCosTam();

Wtedy w klasie SomeStrategy

public StrategyImplementation get(InputData inputData){
switch(inputData)
case A:
new AStrategyImplementation(mojBeanZWarstwyWyzej);
return
}

No ale jeśli tych warstw klas byłoby kilka to musiałbym to przepychać, prawda?

  1. Podobne rozwiązanie tylko, że zamiast w konstruktorze to wepchnąć tego beana do StrategyImplementation#zrobCosTam

  2. W miejscu w którym chce wykorzystać tego beana wołać:

        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SomeConfigurationABC.class);
        MojBeanKtoregoChce bean = annotationConfigApplicationContext.getBean(MojBeanKtoregoChce.class);

Nie mam pojęcia jakie są plusy - czy w ogóle są - a jakie minusy takiego rozwiązania.

Przepraszam za moją ułomność ale naprawdę czuję się zagubiony :D

2
an0ny napisał(a):

No dobrze: mam już tą klasę z @Configuration w której tworzę sobie beany. Istnieje jakiś serwis w którym te beany wstrzykuje i wszystko jest w porządku - śmiga. Jedna z metod w tym serwisie tworzy sobie jakąś tam strategię - przesyłam do innej klasy (stworzonej przez new) jakieś parametry wejściowe i w wyniku dostaję konkretną implementację (również stworzonej przez new) jakiegoś tam interfejsu. No i teraz w środku tej metody w serwisie wołam na tej implementacji metodę zrobCosTam(). W środku zrobCosTam() potrzebowałbym dostępu jednego z Beana, którego mam zdefiniowanego w klasie z @Configuration. Cały czas nie rozumiem jak mógłbym to zrobić.

Mógłbyś pokazać obecny kod i powiedzieć co chcesz gdzie użyć? Bo nie rozumiem jaki jest problem :)

1

@an0ny no tak, musiałbyś "przepychać" ale tak to już jest w życiu :D Na tym polega dependency inversion, że potrzebne zależności "dostajesz" gdzieś z góry, co wiąże się z ich "przepychaniem".
Nie bardzo tylko rozumiem konktekst, bo akurat Strategia to taka sytuacja gdzie masz ich z góry ograniczoną liczbę i można z nich też zrobić singletony... To o czym pisałem ma sens jeśli masz jakiś obiekt domenowy który potrzebuje tego twojego serwisu.

0
Pinek napisał(a):

Mógłbyś pokazać obecny kod i powiedzieć co chcesz gdzie użyć? Bo nie rozumiem jaki jest problem :)

Pewnie! Kod rozgrzebany no ale mogę pokazać, gdzie jest to miejsce o którym mówię:
https://github.com/4pan0ny/szybkiewakacje

pl.szybkiewakacje.rest.notification.NotificationService#saveNewNotificationForms

Jest tam taka mapa, gdzie kluczem są rodzaje sposobu komunikacji po którym ktoś ma dostać powiadomienie, wartością natomiast jest dany adres.
Dajmy na to Entry<EMAIL, "[email protected]>, Entry.<SMS, +48 123123123>, Entry<FACEBOOK, facebook.com/user/1233532>.
Dla każdego tego enuma tworzona jest inna strategia zapisu takiego wpisu do bazy danych.

Teraz chciałbym dwie warstwy (przypuśćmy RegistrationForSMSNotificationStrategy.class) niżej móc odwołać się do jakiegoś beana - mam gdzieś tam serwis do sprawdzania poprawności niektórych danych czy beana do zapisywania encji. Nie wiedziałem właśnie czy jest jakiś sprytny sposób, żeby to tam wstrzyknąć, czy będę musiał przepychać a może w ogóle nic nie wstrzykiwać przez kontener i wszystko robić po ludzku przez new.

2
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(NotificationConfiguration.class);
NotificationDAO notificationDAO =  (NotificationDAO) annotationConfigApplicationContext.getBean("notificationDAO");

Set<TrackedForNotificationEntity> all = notificationDAO.getAll().stream().filter(e -> e.getOfferId() == reservationId).collect(Collectors.toSet());
return all;

Po co robisz takie coś? Przecież właśnie to NotificationDAO powinieneś wstrzyknąć tak jak wstrzykujesz ReservationDAO. W dodatku wstrzykujesz przed konstruktor ReservationDAO oraz VerifierService, a potem ich nie używasz. Ogólnie chyba problem polega na tym, że nie do końca rozumiesz jeszcze jak bazowo działa Context Springa i jak wstrzykiwać beany.
Ogólnie jak już chcemy najmniej tych @Service, @Inject i @Autowired. To właśnie nasz jakaś fasada powstaje w klasie konfiguracyjnej mniej więcej tak:

@Configuration
class SampleConfig {
   @Bean
   SampleFacade sampleFacade() {
          return new SampleFacade(new SampleVerifier(), new SampleDAO, new SampleCreator());
   }
}

Wtedy nie musisz bawić się w adnotacje @Service itp.

0
lavoholic napisał(a):

Po co robisz takie coś?

To jest rozkopany kod - tutaj tylko sobie sprawdzałem jak działa context.getBean(). Wrzucając ten kod chciałem pokazać jedynie to miejsce ze strategią i "przepychaniem" jakiegoś beana dalej.

A co do drugiej części Twojego komentarza:
To znaczy, że dobrą praktyką jest, żeby dla każdego pakietu zrobić sobie taką klasę z @Configuration, która będzie tworzyła takie beany?
Jak już wrzuciłem mój kod, to może będę się na nim bazował: mam katalog pl.szybkiewakacje.rest.util w którym znajduję się serwis VerifierService. Zgodnie ze sztuką powinienem ściągnąć z niego adnotację @Service i zrobić w tym samym katalogu klasę z @Configuration, tak?


@Configuration
public class UtilConfiguration {
         
       @Bean
       public VerifierService verifierService(){
           PodSerwis1 a = new ...();
           PodSerwis2 b = new ....();
           return new VerifierService(a, b);
}

A jeśli chciałbym go użyć dajmy na to w pl.szybkiewakacje.rest.notification to tam również tworzę podobny byt na tej zasadzie:

@Configuration
public class NotificationConfiguration {
         
       @Bean
       public NotificationService notificationService(VerifierService verifierService, SerwisZTegoPakietu serwisZTegoPakietu){
           return new NotificationService(verifierService, serwisZTegoPakietu);
}

       @Bean
       public SerwisZTegoPakietu serwisZTegoPakietu(VerifierService verifierService){
           return new SerwisZTegoPakietu(verifierService);
}

W ten sposób wstrzyknięcie przez @Autowired będę miał tylko w Controllerze. Dobrze rozumiem?

1

W dużym skrócie tak, tylko np. taki Twój VerifierService jest za bardzo klasą typu Util, Helper - ktoś o tym ostatnio na forum pisał. Masz coś takiego:

@Service
public class VerifierService {

    private final UserRegisterFormVerifier userRegisterFormVerifier;
    private final EmailVerifier emailVerifier;
    private final PhoneNumberVerifier phoneNumberVerifier;
    private final NotifierModelFormVerifier notifierModelFormVerifier;
}

Bardziej skłaniałbym się do tego żeby to nie było jako jeden wielki Util, tylko te Verifiery wrzucić per jedna odpowiedzialność w odpowiednie miejsca. Ogólnie wygodnie jest gdy Twój jeden moduł/pakiet ma jakby jedno wyjście w postaci jakiejś Fasady - tylko to widzą inne moduły/pakiety i tylko tą klasę tworzysz w jakiejś swojej @Configuration.

0
lavoholic napisał(a):

Bardziej skłaniałbym się do tego żeby to nie było jako jeden wielki Util, tylko te Verifiery wrzucić per jedna odpowiedzialność w odpowiednie miejsca. Ogólnie wygodnie jest gdy Twój jeden moduł/pakiet ma jakby jedno wyjście w postaci jakiejś Fasady - tylko to widzą inne moduły/pakiety i tylko tą klasę tworzysz w jakiejś swojej @Configuration.

Właśnie to chciałem osiągnąć tworząc tą jedną byczą klasę. Pozostałe Verifierypackage-private i walidacja jest dostępna tylko z poziomu tego ogólnego utila.
Chociaż przy dodawaniu kolejnych metod sam zacząłem czuć, że coś zaczyna śmierdzieć w tej klasie.

1

No tak mi się wydaje, że lepiej wydzielić te Verifiery per jakiś obiekt domenowy niż czynić Verifier taką "domeną". No, ale to jeszcze niech się bardziej ogarnięci i doświadczeni wypowiedzą, tylko mi się tak wydaje, że ta klasa mogłaby się rozrosnąć za bardzo w przyszłości.

1

Hmm, ja bym zaczął od tego że we frameworkach IoT to framework zarząca aplikacją a nie developer. Programista tylko tworzy konkretne klocki które potem są zbierane, składane do kupy i używane gdzie trzeba.
Ja bym do tego podszedł w ten sposób. Wszystkie klasy które powiedzmy że są w pewnym sensie immutable i odpowiadają za jakąkolwiek logikę, oddaje je w ręce frameworka niech robi z nich singletony.
To weryfikowanie wszystkich serwisów jedną klasą walidującą to trochę śliski temat. Może nie lepiej jakiś apsekt?

0
Korges napisał(a):

To weryfikowanie wszystkich serwisów jedną klasą walidującą to trochę śliski temat. Może nie lepiej jakiś apsekt?

Aspektów nie znam, douczę się. Skoro podsuwasz pomysł to pewnie się sprawdzą.
Dzięki za zainteresowanie ;)

1

Programowanie aspektowe ... dla mnie brrr
A na serię walidatorów są wzorce, myślę głownie o Chain of Responsibility
(ale to być może utraciłem kontakt z wątkiem)

0
AnyKtokolwiek napisał(a):

Programowanie aspektowe ... dla mnie brrr
A na serię walidatorów są wzorce, myślę głownie o Chain of Responsibility
(ale to być może utraciłem kontakt z wątkiem)

Chyba niekoniecznie się tutaj to sprawdzi. Koledzy wyżej zauważyli, że mam jedną "fasadę" którą nazwałem VerifierService, która składa się z innych komponentów jak PhoneNumberVierfier, EmailVerifier, AccountPasswordVerifer etc. I każdy serwis w projekcie zamiast korzystać z konkretnej klasy do walidacji to korzysta z tego VerfierService i tam sobie woła .verifyEmail(), .verifyUserPassword() i kilkanaście innych metod.

Argumentem "za" w mojej głowie było to, żeby wystawić tylko jedną klasę na widok dla innych serwisów.
Argumentem przeciw było to, że ta klasa trochę urosła i zrobiła się dość dziwna - coś schrzaniłem.

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