Circular dependency - problem z zależnościami

0

Drodzy forumowicze,

Mam następujący problemw mojej aplikacji. Aby zobrazować jak najlepiej sytuację: chciałem zaimplementować metodę, która zwróci mi listę jednostek przypisanych do pracownika. Metoda wygląda następująco:

        public async Task<List<WorkplaceDetailsViewModel>> GetWorkplacesByUser()
        {
            var userId = await _userManager.GetCurrentUserId();
            if (userId != null)
            {
                var workplacesIds = _workplacesUsersRepo.GetWorkplacesByUser(userId);
                var listOfWorkplaces = new List<WorkplaceDetailsViewModel>();
                foreach (var i in workplacesIds)
                {
                    listOfWorkplaces.Add(_workplaceService.GetWorkplace(i));
                }
                return listOfWorkplaces;
            }
            else
            {
                var workplacesIds = _workplaceService.GetAllWorkplaces().Select(i => i.Id).ToList();
                var listOfWorkplaces = new List<WorkplaceDetailsViewModel>();
                foreach (var i in workplacesIds)
                {
                    listOfWorkplaces.Add(_workplaceService.GetWorkplace(i));
                }
                return listOfWorkplaces;
            }
        }

Po jej zaimplementowaniu otrzymuję następujący ex:

InvalidOperationException: A circular dependency was detected for the service of type 'Application.Interfaces.IUserManager'.
Application.Interfaces.IUserManager(Application.Services.UserManagerService) -> Application.Interfaces.IWorkplacesUsersService(Application.Services.WorkplacesUsersServices) -> Application.Interfaces.IUserManager

Rozumiem, że chodzi tutaj o to, że w serwisie X mam instancję serwisu Y, a z kolei w serwisie Y w konstruktorze mam utworzoną instancję serwisu X tak?

No i nasuwa się tutaj moje pierwsze pytanie - bo co jeśli akurat w serwisie Y, potrzebuję wykonać metodę z serwisu X? Dokładnie chodzi tutaj o "GetCurrentUserId()".

**I drugie pytanie, załóżmy że mam serwis A, który w konstruktorze zawiera instancję userManagera (.netowe Identity), potrzebuję w serwisie B dostać id obecnie zalogowanego użytkownika. No i teraz - co jest lepszym podejściem:

  1. dodać w konstruktorze serwisu B instancję userManagera i wywołać odpowiednią metodę,
  2. dodać w serwisie A metodę, która tak na prawdę wywoła tylko metodę zawartą już w Identity, a następnie z serwisu B, odwołać się do tej metody z serwisu A? **

Wybaczcie, ale może zamierzam obejść rzekę naokoło zamiast przejść przez most, dlatego pytam...

4
Krispekowy napisał(a):

No i nasuwa się tutaj moje pierwsze pytanie - bo co jeśli akurat w serwisie Y, potrzebuję wykonać metodę z serwisu X? Dokładnie chodzi tutaj o "GetCurrentUserId()".

To łamiesz SRP i ISP.

Krispekowy napisał(a):

**I drugie pytanie, załóżmy że mam serwis A, który w konstruktorze zawiera instancję userManagera (.netowe Identity), potrzebuję w serwisie B dostać id obecnie zalogowanego użytkownika. No i teraz - co jest lepszym podejściem:

  1. dodać w konstruktorze serwisu B instancję userManagera i wywołać odpowiednią metodę,
  2. dodać w serwisie A metodę, która tak na prawdę wywoła tylko metodę zawartą już w Identity, a następnie z serwisu B, odwołać się do tej metody z serwisu A? **

Powinien być "serwis" odpowiedzialny za obsługę zalogowanego użytkownika, jego to powinieneś przyjąć w B i wywołać metodę. Czy to będzie serwis A czy jakiś inny, to już zależy od SOLID i GRASP.

0

Ok mam w sumie jeszcze jedno pytanie @Afish . No bo klasa Identity ma m.in. metodę, która zwraca Id zalogowanego usera. Czy jest sens tworzyć klasę dla obsługi zalogowanego użytkownika i w niej zawrzeć metodę "GetCurrentUserId()"? Nie wiem czy nie przekombinowuję teraz bo wydaje mi się, że duplikuję kod, który już ktoś zbudował, a potrzebuję w klasie/serwisie otrzymać Id zalogowanego użytkownika, aby zwrócić mu listę jednostek, ten serwis będzie miał też inne metody m.in. dodawanie, usuwanie, modyfikacja jednostek.

1
Krispekowy napisał(a):

No bo klasa Identity ma m.in. metodę, która zwraca Id zalogowanego usera. Czy jest sens tworzyć klasę dla obsługi zalogowanego użytkownika i w niej zawrzeć metodę "GetCurrentUserId()"?

To jest dobre pytanie. Zasadniczo Identity traktujesz jako "zewnętrzną zależność", czysto teoretycznie możesz kiedyś wymienić mechanizm autentykacji (przekreślam, bo zaraz mnie zjedzą) uwierzytelnienia/autoryzacji (na przykład dodać coś nieobsługiwanego przez Identity), więc "powinieneś" opakować to w swój interfejs, ale nie wiem, czy coś Ci to da, bo pewnie po stronie Identity jest jakiś interfejs, który możesz w razie czego mockować i takie tam. Jeżeli ta Twoja klasa miałaby delegować do Identity niemal "1 do 1" (nawet nazwy metod by się zgadzały czy coś), to ja bym póki co to zignorował i dodał swoją abstrakcję kiedyś w przyszłości w razie potrzeby. Pewnie w 95% przypadków i tak wystarczy Ci to, co dają wbudowane mechanizmy od MS.

0
Afish napisał(a):

To jest dobre pytanie. Zasadniczo Identity traktujesz jako "zewnętrzną zależność", czysto teoretycznie możesz kiedyś wymienić mechanizm autentykacji (przekreślam, bo zaraz mnie zjedzą) uwierzytelnienia/autoryzacji (na przykład dodać coś nieobsługiwanego przez Identity), więc "powinieneś" opakować to w swój interfejs, ale nie wiem, czy coś Ci to da, bo pewnie po stronie Identity jest jakiś interfejs, który możesz w razie czego mockować i takie tam. Jeżeli ta Twoja klasa miałaby delegować do Identity niemal "1 do 1" (nawet nazwy metod by się zgadzały czy coś), to ja bym póki co to zignorował i dodał swoją abstrakcję kiedyś w przyszłości w razie potrzeby. Pewnie w 95% przypadków i tak wystarczy Ci to, co dają wbudowane mechanizmy od MS.

Generalnie masz 100% racji, bo póki co Identity w zupełności pokrywa te funkcjonalności, które chciałem zaimplementować.

Ze swoim problemem poradziłem sobie w ten sposób, że faktycznie wydzieliłem klasę (serwis), który zawiera metody potrzebne do obsługi zalogowanego użytkownika. Nie robiłem osobnej metody do pobrania Id w innym serwisie, aby następnie ją wywołać, skoro Identity ma to już obsłużone to po prostu korzystam z tego w swoich metodach (w tych, w których akurat potrzebuję Id). Chciałem nawet dodać Id usera zalogowanego w konstruktorze, ale wali mi błędami z dependency injection odnośnie stringa. Ale to już tzw. pierdoła, więc na razie będę wywoływał każdorazowo tą metodę Identity w odpowiednich moich metodach.

Dzięki za pomoc!

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