Zadanie rekrutacyjne i logika w API

0

Witam szanowne grono forumowiczów,

Ktoś odkopał wczoraj temat z przed 2 lat (Zadanie rekrutacyjne) i akurat jeden post rzucił mi się w oczy. Chciałbym poruszyć kilka kwestii odnośnie proponowanego rozwiązania, dla czytelności przekleje cały kod tutaj.

public class User
{
    public Guid Id { get; protected set; }
    private decimal _balance;
    public decimal getBalance => _balance;
    private static readonly object _lock = new object();

    public void Withdraw(decimal value)
    {
        lock (_lock)
        {
            if (value >= balance)
            {
                _balance -= value;
            }
        }
    }
}

public static class UserService
{
    public static ActionResult Perform_Withdraw_Logic(User user, decimal value)
    {
        if (value <= 0)
        {
            return new BadRequestResult("Amount of money to withdraw must be greater than 0");
        }
        if (value > user.getBalance)
        {
            return new BadRequestResult("You've not enough money");
        }

        user.Withdraw(value);

        return new OkRequestResult("Money has been withdrawn successfully");
    }
}

public class Controller
{
    public HomeController : Controller
    {
        [Authorize]
        public ActionResult WithdrawMoney([FromBody] WithdrawCommand)
        {
            var user = db.Single(c => c.Id == WithdrawCommand.Id);

            if (user == null)
            {
                return new BadRequestResult("User not found");
            }

            return UserService.Perform_Withdraw_Logic(user, WithdrawCommand.Amount);
        }
    }
}

Otóż zastanawia mnie kilka kwestii.

  1. Czy klasa User jest w tym kontekście modelem? Co tam robi metoda Withdraw? W podejściu ktore sam praktykuję modele zawierają propertisy, a logika operacji jakie mogą zostać wykonane na modelu znajduje się w serwisie.
  2. Jak zaimplementowany jest UserService? Czy nie powinien on implementować serwisu IUserService kóry byłby wstrzykiwany do Controllera i stamtąd wywoływane odpowiednie metody?
  3. Czy przez okres 2 lat coś się zmieniło w temacie architektury? Od momentu kiedy został utworzony temat Zadania rekrutacyjnego do dzisiaj.
    4. Jak w podejściu które sam praktykuje zabezpieczyć się przed dostępem wielu wątków do jednego zasobu? Czy proponowany poniżej sposób jest poprawny?
public class UserService : IUserService
{
   private readonly DbContext _db;
   private object _lock = new object();


   public UserService(DbContext db)
   {
      _db = db;
   }
   public void Update(User user)
   {
      lock(_lock)
      {
         _db.Users.Update(user);
      }
   }
}
2
  1. Jeśli klasa domenowa zawiera w sobie komplet informacji potrzebnych do przeprowadzenia sensownej operacji to po co serwis?
  2. UserService nie powinien zwracać ActionResult, wydzielenie interfejsu ma sens jak chcesz go zmockować w testach, kontener IoC może wstrzykiwać tez typy konkretne.
  3. Dlaczego w ogóle chcesz współdzielić DbContext między wątkami?
0
  1. Tutaj mimo, że jestem nowy w tematach DDD to przychodzi mi na myśl DDD właśnie. Według tego podejścia model nie ma być anemiczny tylko ma pokazywać zachowania danej klasy, jego domenę bazując na modelu a nie serwisach w Twoim rozumowaniu. Na pewno nie ma być zestawem publicznych geterów i seterów.

Przykładowo, chcąc zmienić hasło dla użytkownika nie będę odnosił się do propertiesa

user.Password("123")

tylko do metody modyfikującej ten obiekt, która zawiera w sobie walidacje i jakieś inne dodatkowe logiki itp. To obiekt ma dbać o takie rzeczy jak swój stan.

user.SetPassword("123")

Proszę o poprawę jeśli się mylę sam próbuję zrozumieć te tematy.

2

Okej, a w podejściu gdzie model jest anemiczny w jaki sposób ktoś może stworzyć niepoprawny model z punktu widzenia logiki biznesowej skoro po stronie serwisu jak i GUI zaimplementowana jest walidacja?

No tak, bo programiści nie popełniają błędów, nie zapominają o uzupełnieniu pól, w ogóle jak jest walidacja to nic złego się stać nie może. Ograniczenie możliwości i hermetyzacja już na poziomie obiektu domenowego zamyka możliwości tego co można w nim popsuć. A psują zwykle programiści, a nie użytkownicy.

@Saalin: zakładając, że chcesz dodać obiekt do bazy jak to zrobisz? Jak ten obiekt jest zmapowany na obiekt np. EF?

@Krzysztof Pe: nie współdzieliłbym modelu bazodanowego z modelem domeny i mapowałbym jedne na drugie.

3
Krzysztof Pe napisał(a):

W podejściu ktore sam praktykuję modele zawierają propertisy, a logika operacji jakie mogą zostać wykonane na modelu znajduje się w serwisie.

Ale serwis też jest częścią modelu. Model to warstwa aplikacji modelująca to, czym aplikacja się zajmuje, a nie klasa z właściwościami.

To, co Ty stosujesz to wzorzec Transaction Script. Autor tamtego kodu starał się mieć Rich Domain Model, co jest o tyle inne, że obiekty są obiektami w sensie OOP, a nie rekordami.

  1. Jak zaimplementowany jest UserService? Czy nie powinien on implementować serwisu IUserService kóry byłby wstrzykiwany do Controllera i stamtąd wywoływane odpowiednie metody?

Lepiej wstrzyknąć, lepiej nie robić statycznie, lepiej nie zwracać obiektów infrastruktury webowej i lepiej nie implementować interfejsu.

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