Clean architecture - rejestracja i logowanie użytkowników

1

Ciąg dalszy zagwozdek z Clean Architecture.
Powiedzmy, że korzystam z ASP.NET Core Identity i chcę mieć możliwość rejestracji użytkowników.

ASP.NET Core Identity wymaga swojego modelu użytkownika - a raczej tego żeby dziedziczył po IdentityUser. Więc wiadomo, że to musi być zaimplementowane po stronie infrastruktury, a nie warstwy Core mojej aplikacji, bo w Core nie chcę zależności do innych frameworków.

Ale gdzie teraz obsłużyć rejestrację/logowanie/resetowanie hasła itp.?
Oczywiste wydawało mi się, że nie w Core, bo nie jest to część związana z domeną mojej aplikacji. Jakbym nie trzymał użytkowników po swojej stronie tylko korzystał z jakiegoś zewnętrznego Identity Providera to wtedy Core nic by o takich pojęciach jak logowanie czy rejestracja nie wiedział, więc i teraz raczej też nie powinien.

Ale jak nie chcę robić tego w Core to gdzie?
Niby mógłbym to zrobić w webie i wtedy z kontrolera zamiast wołać jakiś biznesowy UseCase bym po prostu wołał jakiś serwis odpowiedzialny za uwierzytelnianie/logowanie (który pod spodem korzystałby z klas dostarczonych przez ASP.NET Core Identity - SignInManager i UserManager), ale web nie powinien mieć bezpośrednio zależności do infrastruktury, wtedy w warstwie web definiować jakiś interfejs, który byłby zaimplementowany przez infrastrukturę (raczej przyzwyczajony jestem do tego że to Core definiuje interfejsy, które są implementowane przez infrastrukturę)? Druga kwestia jest taka, że według teorii web to tylko jeden punktów wejścia do apki i mógłby istnieć jakiś inny, no ale powiedzmy że to tylko teoria, bo jak mam apkę webową to raczej nie będzie miała innego wejścia od strony usera (np. jakiegoś CLI które często jest podawane w przykładach architektury heksagonalnej).

Pozwolę sobie zawołać @somekind @Aventus @ProgScibi

3

U zewnętrznego providera ;) Imho ogólnie nie wszystko w microsoft im wyszło, Identity to straszna kobyła, dużo boilerplate, ciężkie w ogarnięciu, nieintuicyjne - a i tak w większości przypadków istnieje jakaś domena AD, do której tylko wystarczy apkę podpiąć, a wszystkie te trudne i żmudne rzeczy zostawić w spokoju.

Ewentualnie OKTA, Auth0, i wtedy mieć jakąś fasadę na ich api, żeby pobierać obrazek usera i jakieś metadane.

1

ASP.NET Core Identity wymaga swojego modelu użytkownika - a raczej tego żeby dziedziczył po IdentityUser.

To nie do końca prawda. Jakiś czas temu dawałem tu na forum link do mojego przykładu z własną implementacją Identity: https://github.com/aventus13/DecoupledIdentity

Co do reszty to wypowiem się jak będę miał więcej czasu.

0

Identity to osobny bounded context/moduł może być. Z punktu widzenia ficzerów nie jest istotne jakie hasło ma user (a może autoryzuje się przez Facebooka?) - to wskazuje na to, że masz więcej niż jedną encję „User” w systemie. Podziel to wertykalnie na moduły, moduł Identity nawet nie musi być wtedy ogarniamy Clean Architecture (a nawet może być użyty jakiś gotowiec z zewnątrz typu Auth0).

0
Charles_Ray napisał(a):

Identity to osobny bounded context/moduł może być.

@Charles_Ray Ale według mnie nie w Core aplikacji (czyli też chyba nie powinniśmy nazywać tego bounded contextem - to mi się kojarzy z domeną), bo jak sam dalej mówisz, użytkownik mógłby się logować facebookiem i samych reguł/procesów biznesowych to w ogóle nie powinno interesować.

Charles_Ray napisał(a):

Podziel to wertykalnie na moduły, moduł Identity nawet nie musi być wtedy ogarniamy Clean Architecture (a nawet może być użyty jakiś gotowiec z zewnątrz typu Auth0).

Wertykalnie na moduły mam podzielony tylko Core aplikacji.

Mogę zrobić po prostu oddzielny moduł (ale jak pisałem raczej nie w Core) na Identity i wołać to sobie bezpośrednio z kontrolera.

//Edit:
A jak teraz podejść do sytuacji w której w reakcji na rejestrację użytkownika w systemie chcę stworzyć biznesową encję użytkownika (czyli już tą która nie wie o tym jakie hasło ma użytkownik, itp.).
Gdybym miał Identity jako oddzielny kontekst w Core to rzuciłbym pewnie event UserRegistered i inny moduł w Core by na niego zareagował i stworzył coś po swojej stronie, ale jak nie mam tego w Core to nie mogę tak zrobić.
Jedyne sensowne co mi przychodzi do głowy to po prostu po zarejestrowaniu użytkownika odpalić jeszcze tworzenie biznesowego usera za pomocą UseCase'a z Core.

0

Złożony core domain tez powinien być podzielony na mniejsze subdomeny, wzorce typu dziel i zwyciężaj aplikują się uniwersalnie :)

Jeśli chcesz stworzyć „encje biznesową” (w ogólności N takich encji w N różnych kontekstów) to najkoszerniej zrobić pub-sub (wtedy domena Core nic nie wie o Identity). Wersja uproszczona to tworzenie synchroniczne tak jak napisałeś, ale wprowadzasz coupling i masz niższe SLA procesu rejestracji (dużo większa szansa, że się nie powiedzie i będzie długo trwał, chyba nikt tak by nie zrobił produkcyjnie).

0

@Charles_Ray: Dla uściślenia terminologii.
Za Core uważam dwie wewnętrzne warstwy z Clen Architecture - czyli Use Case i Domain.

Ten Core mam podzielony na subdomeny typu Meetings, Payments, czy właśnie np. Members. Najlepiej byłoby nie mieć tutaj nic związanego z użytkownikami (ale w technicznym rozumieniu czyli hasło, token do potwierdzenia rejestracji itp.), czyli nie mam subdomeny Users czy tam Identity, która wiedziałaby coś o sposobie rejestracji/uwierzytelniania użytkowników.

I teraz w reakcji na rejestrację użytkownika, która jest robiona całkiem oddzielnym modułem (poza warstwą Core) chcę stworzyć encję Member.
Rzucenie jakiegoś eventu (czy to jest inmemory bus, czy jakiś broker typu rabbit to teraz nie ważne) to też było pierwsze co mi przyszło do głowy, ale czy w takiej architekturze eventy nie powinny być emitowane tylko z warstwy domenowej, czyli u mnie z Core?

Osobiście jakiegoś dużego problemu nie widzę w tym, żeby w Core zrobić sobie event UserRegistered, który byłby emitowany z modułu identity (leżacy poza Core), a na którego miałbym listenera w module domenowym Members, no ale czy tak powinno się robić?

2

Mamy w takim razie pewien misunderstanding tutaj :) Każda subdomena ma własny kod domenowy (agregaty, encje, serwajsy, …) i może sobie rzucać eventy. Dlaczego tylko Core może je emitować? Potraktuj każdy moduł jako niezależna aplikację, z własna architekturą. Dzięki temu nie każdy moduł będziesz musiał robić hexagonem, dla niektórych CRUD będzie Ok.

0
Charles_Ray napisał(a):

Każda subdomena ma własny kod domenowy (agregaty, encje, serwajsy, …) i może sobie rzucać eventy.

No właśnie, każda subdomena. A logowanie i rejestracja przecież nie jest subdomeną :P

Charles_Ray napisał(a):

Dlaczego tylko Core może je emitować?

Core to moja umowna nazwa projektu/folderu/warstwy gdzie mam subdomeny.

Podział aplikacji widziałem mniej więcej tak (obojętnie czy to foldery, projekty, namespace'y itp.)

  • Xyz.Web
  • Xyz.Core (albo Xyz.Domain, albo Core rozbity na Xyz.Application i Xyz.Domain)
    • Xyz.Core.Members
    • Xyz.Core.Meetings
      ...
  • Xyz.Infrastructure

Czyli raczej dość standardowy z różnych sampli związanych z clean architecture/hexagonem w .net/asp.net core.

Gdzie w Core trzymam biznesowe przypadki użycia i encje domenowe dla każdej subdomeny.
Ale w takim podejściu nie pasuje mi moduł Identity w Core, bo on nie jest związany z biznesowym przypadkiem użycia. Więc zrobiłbym po prostu leżący obok moduł Xyz.Identity i tam ogarniał logowanie/rejestrację itp. Ale teraz w Xyz.Core.Members chciałbym mieć listener na akcję wykonaną w Xyz.Identity. Xyz.Core.Members jest warstwą wewnętrzną, wiec nie powinna mieć bezpośrednio zależności do Xyz.Identity. Mogę w tym przypadku po prostu w Core zrobić coś w stylu Xyz.Core.Common (czy Shared, czy SharedKernel, znowu różnie się to nazywa) i tam definicję eventu UserRegistered.

Taki był mój proces myślowy :P

Chyba, że tak jak piszesz, całą apkę podzielę bardziej wertykalnie (bo teraz jak wspominałem wertykalnie podzieloną mam tylko warstwę biznesowych przypadków użycia i domenową, a np. wszystkie kontrolery mam w Web), a każdy moduł będzie definiował swoje eventy i je rzucał.

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