CQRS Login Command - co powinna zwracac?

1

Witam,

Szukałem na forum i nie mogę nigdzie znaleźć satysfakcjonujacego mnie rozwiązania.
Mianowicie mam aplikacje (bardziej początek aplikacji). używam MediatR do obsługi CQRS.
Co powinna zwracać komenda logowania użytkownika?
Początkowo zwracałem z komendy model LoggedUserModel do kontrolera który zawierał jwt token + dodatkowe informacje potrzebne we front-end'e (front end na angular2 - osobna alikacja), ale co w przypadku kiedy logowanie się nie powiedzie? Może dodać w modelu pozycje "status" etc? Z kontrolera do klienta zwracam IActionResult.
Czy może z komendy zwrocic jeden model a następnie w zależnosci od "statusu" zwracać inne IActionResult? Zwracanie ActionResult z komendy raczej odpada?
Moj kod "so far..." :
Controller: (kawałek logowania):

[HttpPost("login")]
public async Task<ActionResult<LoggedUserModel>> Login([FromBody] LoginUserCommand c)
{
    return Ok(await _mediator.Send(c));
}

Command Handler: (funkcja handle):

public async Task<LoggedUserModel> Handle(LoginUserCommand request, CancellationToken cancellationToken)
{
    var user = await _context.Users.Where(u => u.Email == request.Email).SingleOrDefaultAsync(cancellationToken);
    if (user == null)
              =>   ; // co tutaj zwrocic?
    
    var salt = user.Salt;
    var hash = _encrypter.GetHash(request.Password, salt);
    if(user.Password == hash)
    {
        var token = _tokenHandler.GetToken(user.Email, user.Role);
        var LU = new LoggedUserModel
        {
            // testowy loggedusermodel
            Id = user.Id,
            Token = token,
        };
        return LU;
   } else {
       // co zwrocic w przypadku blednego logowania?
   }
}

Prosiłbym o wyrozumiałość z powodu że jestem początkujący :D
Myślałem nad kilkoma rozwiązaniami ale chciałbym usłyszeć jakieś rady od bardziej doświadczonych żeby to napisać "dobrze".

Dzięki.

0

Zamiast Task<LoggedUserModel> zwracaj np obiekt Response

public class Response
    {
        private readonly IList<string> _messages = new List<string>();

        public IEnumerable<string> Errors { get; }
        public object Result { get; }

        public Response() => Errors = new ReadOnlyCollection<string>(_messages);

        public Response(object result) : this()
        {
            Result = result;
        }

        public Response AddError(string message)
        {
            _messages.Add(message);
            return this;
        }
    }

I swój model usera trzymaj w Result,

1
szydlak napisał(a):

Zamiast Task<LoggedUserModel> zwracaj np obiekt Response

public class Response
    {
        private readonly IList<string> _messages = new List<string>();

        public IEnumerable<string> Errors { get; }
        public object Result { get; }

        public Response() => Errors = new ReadOnlyCollection<string>(_messages);

        public Response(object result) : this()
        {
            Result = result;
        }

        public Response AddError(string message)
        {
            _messages.Add(message);
            return this;
        }
    }

I swój model usera trzymaj w Result,

To jest łamanie idei CQRS'a. Poza tym jak to będzie asynchroniczna komenda (na poziomie infrastruktury) to i tak tego nie zwrócisz.

0

Zgadzam się. Tylko jak weryfikować czy te komendy się powiodły czy nie? Exceptiony też raczej nie bardzo. Nie mówię już nawet o samym logowaniu. Ale inne operacje. Dodawanie nowego usera itp

2

Tylko jak weryfikować czy te komendy się powiodły czy nie?

Po stronie aplikacji powinieneś stworzyć event (UserLoggedIn, UserCreated itd.) na który klient powinien nasłuchiwać (za pomocą webhooków, kolejek, czegokolwiek); w evencie możesz już mieć flagę czy operacja się powiodła.

3
Patryk27 napisał(a):

Tylko jak weryfikować czy te komendy się powiodły czy nie?

Po stronie aplikacji powinieneś stworzyć event (UserLoggedIn, UserCreated itd.) na który klient powinien nasłuchiwać (za pomocą webhooków, kolejek, czegokolwiek); w evencie możesz już mieć flagę czy operacja się powiodła.

Stroniłbym od takich flag w evencie UserCreated. Raczej lepiej byłoby zrobić 2 eventy. Jeden dla sukcesu drugi dla porażki. Ogólnie taki prawdziwy CQRS niesie ze sobą dużo pozytywów, ale wdrożenie go i przekonanie biznesu do eventual consistency to nie jest łatwy kawałek chleba. W większości projektów kolejki, websockety dla komend przy operacjach typu CRUD itp to overengineering, a niestety 80% aplikacji to CRUDy. Tylko programiści lubią utrudniać sobie życie podążając za modą z konferencji (microserwisy itd.)

0
Patryk27 napisał(a):

Tylko jak weryfikować czy te komendy się powiodły czy nie?

Po stronie aplikacji powinieneś stworzyć event (UserLoggedIn, UserCreated itd.) na który klient powinien nasłuchiwać (za pomocą webhooków, kolejek, czegokolwiek); w evencie możesz już mieć flagę czy operacja się powiodła.

A masz gdzieś przykładową implementację z wykorzystaniem MediatR?

0
szydlak napisał(a):
Patryk27 napisał(a):

Tylko jak weryfikować czy te komendy się powiodły czy nie?

Po stronie aplikacji powinieneś stworzyć event (UserLoggedIn, UserCreated itd.) na który klient powinien nasłuchiwać (za pomocą webhooków, kolejek, czegokolwiek); w evencie możesz już mieć flagę czy operacja się powiodła.

A masz gdzieś przykładową implementację z wykorzystaniem MediatR?

Tutaj nie ma magii. Publikujesz event w handlerze i coś się na niego subsrkybuje. To coś operuje na websocketach i przesyła potrzebne informacje do klienta.

0

Bez sensu. Informacja stanie przetwarzania żądania nie jest zasobem w sensie CQRSa.

Czyli każda komenda będzie miała oddzielny event ze statusem aplikacji, super!.

0
Błękitny Terrorysta napisał(a):

Bez sensu. Informacja stanie przetwarzania żądania nie jest zasobem w sensie CQRSa.

Czyli każda komenda będzie miała oddzielny event ze statusem aplikacji, super!.

Jak potrzebujesz to publikujesz. Jak wsadzisz taką komende na kolejke to jak chcesz dostać wynik ?

0

Jak wsadzisz taką komende na kolejke to jak chcesz dostać wynik ?

Takie decyzje projektowe podejmuje się z powodu skalowalności, a nie samego CQRSa, który jest głównie podziałem obiektów na Q/R.

0

C/Q :P

0

Podziałem na C/Q jest CQS a nie CQRS. A wracajac to tutaj został podany przykład zwracania użytkownika po wyslaniu LoginCommand wiec to jest zasob. Można to robić ale to jest łamanie CQRSa i tyle.

0
error91 napisał(a):

Podziałem na C/Q jest CQS a nie CQRS. A wracajac to tutaj został podany przykład zwracania użytkownika po wyslaniu LoginCommand wiec to jest zasob. Można to robić ale to jest łamanie CQRSa i tyle.

W CQS chodzi o metody, a nie obiekty.

https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf

Command and Query Responsibility Segregation uses the same definition of Commands and Queries
that Meyer used and maintains the viewpoint that they should be pure. The fundamental difference is
that in CQRS objects are split into two objects, one containing the Commands one containing the
Queries.

A rzucenie wyjątku to, też zasób?

0
Błękitny Terrorysta napisał(a):
error91 napisał(a):

Podziałem na C/Q jest CQS a nie CQRS. A wracajac to tutaj został podany przykład zwracania użytkownika po wyslaniu LoginCommand wiec to jest zasob. Można to robić ale to jest łamanie CQRSa i tyle.

W CQS chodzi o metody, a nie obiekty.

https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf

Command and Query Responsibility Segregation uses the same definition of Commands and Queries
that Meyer used and maintains the viewpoint that they should be pure. The fundamental difference is
that in CQRS objects are split into two objects, one containing the Commands one containing the
Queries.

A rzucenie wyjątku to, też zasób?

Gdzie tak napisałem? W tym dokumencie który podesłałeś jest wyraźnie że client dostaje ACK/NAK Response. Dla Ciebie zwrócenie zalogowanego usera to jest ACK ? Przy dodawaniu produktu też zwrócisz Id ?

0

Ale się narobiło :D
A co jakbym np zwrocil do controllera loggedUserModel ( jakeis dane i np status wykonania operacji) a w kontrolerze w zaleznosci od statusu zwrocic inny IActionResult z modelem|? Albo dopisac jakas logike ktora to zautomatyzuje.
Męczy mnie to jak to rozwiązać, tak samo np z dodawaniem postu lub innymi operacjami - jak zwrocic czy to sie udalo/nie udalo/rozsypalo etc itp itd..

0

Za bardzo się przejmujesz. Każdy programista ma swoje racje i nie ma jednej poprawnej drogi. Ja z reguły robię tak jak Ci pokazałem na początku. I nie stosuje mediatR, żeby mówić, że mam CQRS, tylko uważam to za ciekawe rozwiązanie. Wcześniej robiłem np service, który szybko się rozrastał i ciężko była nad tym zapanować.Możesz to zrobić na eventach jak tu podpowiadają tylko czy twój projekt na pewno tego wymaga ?

0

Gdzie tak napisałem? W tym dokumencie który podesłałeś jest wyraźnie że client dostaje ACK/NAK Response. Dla Ciebie zwrócenie zalogowanego usera to jest ACK ? Przy dodawaniu produktu też zwrócisz Id ?

Tak dla mnie to jest ACT, ponieważ jest to informacja o dokonanej zmianie.
Poza tym ORM może zwrócić błąd, że w pamięci posiada obiekty z tym samym Id on wcale niemusi czytać czegoś w runtime z DB.
A to, że aplikacja wie o tym, kiedy ściągnąłeś coś ze stack'a też jest problemem?.

The Application Server will return to the client either an Acknowledgement (Ack)
that the change has been persisted or it will return a series of errors as to why it was unable to persist
the changes

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