jak modelowaç szybko i źle | jak zapewnić poprawność obiektu

0

Dear architekci,

jak sensownie zapewnić aby stan obiektu był poprawny?


Wyjdźmy od aktualnego trendu w OOP, a mianowicie modelus anemicus

public class Company
{
    public string Name { get; set; }

    public string NIP { get; set; }
}

var company = new Company
{
    Name = "Fast Company",
    NIP = "123"
};


company.NIP = "🐖🐖" // OK

Nie mamy tu żadnego zabezpieczenia przed wprowadzeniem obiektu w niepożądany stan, a zatem słabo.

public class Company
{
	private string _name;

	public string Name
	{
		get { return _name; }

		set
		{
			if (string.IsNullOrEmpty(value))
				throw new ArgumentException(nameof(value));

			_name = value;
		}
	}

	private string _nip;

	public string NIP
	{
		get { return _nip; }

		set
		{
			if (string.IsNullOrEmpty(value))
				throw new ArgumentException(nameof(value));

			if (!NIPValidator.IsOK(value))
				throw new ValidationException("Zly NIP");

			_nip = value;
		}
	}
}

public static class NIPValidator
{
	public static bool IsOK(string s)
	{
		return s.All(x => char.IsDigit(x));
	}
}

var company = new Company
{
    Name = "Fast Company",
    NIP = "6123123406" // OK
};

company.NIP = "abc"; // FAIL

W tym podejściu nasz obiekt potrafi się wybronić, ale głównie dlatego że w/w przykłady są naïveté

Załóżmy że nagle potrzebujemy walidować czy np. NIP jest unikatowy i co teraz?

Z jednej strony gdyby NIPSetter miał dostęp do bazki przez jakąś abstrakcję, to w sumie by to działało.

public class Company
{
    public Company(string name, string nIP, INIPValidator validator)
    {
        Name = name;
        NIP = nIP;
        Validator = validator;
    }

    public string Name { get; private set; }

    public string NIP { get; private set; }
	
    private INIPValidator Validator { get; }

    public Result<bool> SetNIP(string s)
    {
        if (Validator.IsOk(s))
        {
            NIP = s;
            return new Result<bool>();
        }
        else
        {
            return new Result<bool>("Zly NIP");
        }
    }
}

public class NIPValidator : INIPValidator
{
    public NIPValidator(Bazka bazka)
    {
        Bazka = bazka;
    }

    public Bazka Bazka { get; }

    public bool IsOk(string s)
    {
        return s.All(x => char.IsDigit(x)) && Bazka.IsUnique(s);
    }
}

var company = new Company("Fast Company", "6123123406", new NIPValidator(new Bazka()));
var result = company.SetNIP("asd"); // FAIL.

Ten sposób wygląda dość przyzwoicie, ale bardzo utrudnia tworzenie obiektu, a w szczególności gdy on urośnie
oraz nadal ktoś może "wstrzyknąć" swój NIPValidator i popsuć.
A w dodatku może jednak nie chcemy tego NIPValidatora mieć w tym obiekcie?

Podejście "service based"

public class Company
{
    public string Name { get; set; }

    public string NIP { get; set; }
}

public class CompanyService
{
    public CompanyService(INIPValidator validator)
    {
        Validator = validator;
    }

    private INIPValidator Validator { get; }

    public Result<bool> ChangeNIP(string companyId, string s)
    {
        var company = Bazka.FindCompany(companyId);

        if (company is null)
            return new Result<bool>("404");

        if (!Validator.IsOk(s))
        {
            return new Result<bool>("Zly NIP");
        }

        company.NIP = s;

        return new Result<bool>();
    }
}

var company = new Company
{
    Name = "Fast Company",
    NIP = "123"
};

var service = new CompanyService(new NIPValidator(new Bazka()));

var result = service.ChangeNIP(company.Name, "ABC"); // fail;

Krótko, ale wszystko opiera się o założenie że każdy będzie wiedział o CompanyService i faktycznie będzie go używał zamiast yolo.

Idąc dalej

public class Company
{
    private Company(string name, string nIP)
    {
        Name = name;
        NIP = nIP;
    }

    public string Name { get; }

    public string NIP { get; private set; }

    public class CompanyBuilder
    {
        public CompanyBuilder(Bazka bazka)
        {
            Bazka = bazka;
        }

        public Bazka Bazka { get; }

        private string NIP_Candidate { get; set; }

        private string Name_Candidate { get; set; }

        public CompanyBuilder SetNIP(string s) { NIP_Candidate = s; return this; }
		
        public CompanyBuilder SetName(string s) { Name_Candidate = s; return this; }

        public Result<Company> TryBuild()
        {
            if (string.IsNullOrEmpty(NIP_Candidate) || !IsOk(NIP_Candidate) || !Bazka.IsUnique(NIP_Candidate))
            {
                return new Result<Company>("Zly NIP");
            }

            return new Result<Company>(new Company(Name_Candidate, NIP_Candidate));
        }

        private bool IsOk(string s)
        {
            return s.All(x => char.IsDigit(x));
        }
    }
}

var companyBuilder = new Company.CompanyBuilder(new Bazka());

var result = companyBuilder
             .SetName("Fast Company")
             .SetNIP("6123123406")
             .TryBuild();

no, teraz to już jest state of art ;) /s, ale w sumie to jak zrobić Edit?

i tutaj wydaje mi się, że przychodzi taki szalony koncept, że obiekt po utworzeniu się nie zmienia :D

i (dla uproszczenia) do naszego companyBuilder dochodzi metoda

public class CompanyBuilder
{
	public CompanyBuilder(Bazka bazka)
	{
		Bazka = bazka;
	}

	...
	...
	...
	
    public Result<Company> ChangeCompanyNIP(Company c, string newNIP)
    {
        var builder = new CompanyBuilder(this.Bazka);

        var result = builder
            .SetName(c.Name)
            .SetNIP(newNIP)
            .TryBuild();

		// jeszcze id by wypadało skopiować 

        return result;
    }
}

I koniec końców mamy takie coś:

var companyBuilder = new Company.CompanyBuilder(new Bazka());

var result = companyBuilder
             .SetName("Fast Company")
             .SetNIP("6123123406")
             .TryBuild(); // OK

var changeResult = companyBuilder.ChangeCompanyNIP(result.Company, "abc"); // FAIL

No i tak pi*drzwi to podejście w oparciu o builder mi się podoba,

Wady jakie widzę w Immutable + Builderze to takie że:

  • ktoś nadal może zepsuć podając swoją bazkę

  • performance overhead, no bo aby zmienić jedną rzecz, to trzeba "przeliczyc" cały obiekt.

Oczywiście ta implementacja jest trochę ugly, ale chodzi o sam koncept.

Jeżeli chodzi o implementacje z INIPValidator w Company, to na + była jej mega prostota (przy jednym Validatorze :P).

A zatem wracając do pytania

Jak zgrabnie zapewnić aby nikt nie mógł zepsuł i aby system się bronił, a przy okazji był relatywnie elastyczny?

A może jakieś "mechanizmy z DDD" by tu były pomocne?

2

Builder/Factory (to drugie szczególnie jak potrzebujesz walidować coś za pomocą zewnętrznych serwisów) + obiekty które są niemutowalne, więc w ogóle nie istnieje problem "edycji".

ktoś nadal może zepsuć podając swoją bazkę

? To chyba jako plus, bo argumentem nie powinna być żadna bazka tylko jakaś abstrakcja nad logiką walidujacą (jakiś interfejs) i można wygodnie ten interfejs podmienić np. w testach jednostkowych.

performance overhead, no bo aby zmienić jedną rzecz, to trzeba "przeliczyc" cały obiekt.

Ja bym jednak zrobił w tym builderze jakieś .from() które ustawia parametry względem źródłowego obiektu.

0

@Shalom:

Ja bym jednak zrobił w tym builderze jakieś .from() które ustawia parametry względem źródłowego obiektu.

W sumie faktycznie, dzięki!

Jedynie kod będzie trochę ugly do przepisywania tego, ale generalnie złoto.

.? To chyba jako plus, bo argumentem nie powinna być żadna bazka tylko jakaś abstrakcja nad logiką walidujacą (jakiś interfejs) i można wygodnie ten interfejs podmienić np. w testach jednostkowych.

No plusem jest elastyczność, a wadą to, że ktoś może potencjalnie jakoś zepsuć.

Po to masz np. sealed keyword który blokuje dziedziczenie, aby ktoś nie zepsuł.

10

pole NIP powinno być typu NIP a nie String i po problemie

6

performance overhead, no bo aby zmienić jedną rzecz, to trzeba "przeliczyc" cały obiekt.

To nie jest praktycznie żaden performance overhead.
Największym kosztem nie są na ogół niemutowalne obiekty tylko operacje I/O, chjowe podejście do ORM a nie skopiowanie dwóch pól.
A poza tym sądzę że settery powinny być zniszczone

1
KamilAdam napisał(a):

pole NIP powinno być typu NIP a nie String i po problemie

@KamilAdam ogólnie to powinno być typu NIP, ale przecież ostatecznie w pytaniu chodziło o sposób walidacji tego nipu z bazą na unikatowość.
Więc z typem NIP mamy tą samą sytuację, on sam potrafi się zwalidować z bazą, robi to jakiś builder/factory, czy serwis?

0

@ProgScibi:

To nie jest praktycznie żaden performance overhead.
Największym kosztem nie są na ogół niemutowalne obiekty tylko operacje I/O, chjowe podejście do ORM a nie skopiowanie dwóch pól.

W tamtym momencie chodziło mi o to, że gdy budowany jest ponownie obiekt, to wykonywana jest również walidacja, która to sprawdza coś w bazce.

if (string.IsNullOrEmpty(NIP_Candidate) || !IsOk(NIP_Candidate) || !Bazka.IsUnique(NIP_Candidate))

A zatem nie skopiowanie pola, a db call.

Przekopiowanie niezmienianych wartości ze starego obiektu rozwiązuje ten problem.

0

Klasa modelu nie ma nic wspolnego z walidowaniem od tego masz klase kontrolera i ta robisz ceregiele z baza i sprawdzaniem unikalnosci a nie w klasie obiektu. w ogole nie kumasz obiektowosci

2

i tutaj wydaje mi się, że przychodzi taki szalony koncept, że obiekt po utworzeniu się nie zmienia :D

Na początku trudno uwierzyć jak wiele nierozwiązywalnych problemów rozwiązują niezmienne obiekty

  • Problem kwadratu/prostokąta
  • Problem hashcode/equals w Javie
  • I jeszcze twój problem

performance overhead, no bo aby zmienić jedną rzecz, to trzeba "przeliczyc" cały obiekt.

A z czego w tej chwili składa się twój obiekt? Z nazwy, nipu, referencji na nazwę, referencji na nip, i paru innych meta informacji.
Nip i tak musisz utworzyć nowy, nazwa będzie reużyta między obiektami a referencje i metainformacje dużo nie zajmują.
A zysk naprawdę duży

ktoś nadal może zepsuć podając swoją bazkę

Nawet wiem kto. Do testów prawdopodobnie będziesz musiał podać jakąś inną bazę, więc musisz zostawić furtkę do psucia jak chcesz mieć IoC :)

@KamilAdam ogólnie to powinno być typu NIP, ale przecież ostatecznie w pytaniu chodziło o sposób walidacji tego nipu z bazą na unikatowość.
Więc z typem NIP mamy tą samą sytuację, on sam potrafi się zwalidować z bazą, robi to jakiś builder/factory, czy serwis?

Ale przesuwam problem w trochę inne miejsce i w momencie budowania obiektu Company ufam że mam już dobry NIP.
I tak jak pisałem, czasem sami będziemy chcieli zbudować w prostszy sposób NIP na potrzeby testów więc nie możemy wszystkiego nie wiadomo jak zabetonować. Bardzie trzeba się zabezpieczyć żeby przez pomyłkę nie zrobić złęgo NIPu w kodzie produkcyjnym

Ja bym budował poprawne NIPy w Serwisie, ale ja jestem byłym Javowcem więc wszystko bym robił w Serwisie :D

3

Są rzeczy na niebie i ziemi, o jakich się nie śniło filozofom

A ja się włączę paradoksalnie.

Poprawność stanu względem systemu zewnętrzego to sie wymyka naszym teoriom.
Np deklaracja jest prawidłowa lub nie, zależnie od tego, czy w IT w Ministerstwie ktoś położył kanapkę z pasztetową na routerze, lub nie położył (albo położył, ale z suchą kiełbasą)

Skąd wiecie, ze NIP nie został zdelegalizowany ?

0

Też bym się kierował że Company to struktura z 2 polami, a NIP to ObjectValue. Jeśli zablokujesz możliwość utworzenia obiektu przez coś z poza pakietu, to wymusisz korzystanie z twojego buildera. On z kolei może składać się z dowolnej ilości serwisów od walidacji. Więc wszystko ładnie zostanie przykryte przed użytkownikiem końcowym.

@chomikowski ma rację. Czym innym jest sprawdzenie poprawności wartości w polu, a czym innym sprawdzenie unikalności wartości w systemie. NIP który wpychasz do Company interesuje Cię by był "prawidłowy" więc ObjectValue załatwi Ci tutaj sprawę, a walidacja unikalności wpisu w storage powinna odbywać się przed jego zapisem.

Swoją drogą "encje na twarz i pchasz" to nie najlepszy plan.

8

No jak ktoś chce sobie robić zależności domeny od infrastruktury to nie ok, ale nie oceniam. Ale nie rozumiem po co takie rozkminy i jakaś walka z setterami i pisanie ton kodu, skoro i tak wyjdzie kupa, to lepiej żeby tej kupy objętościowo było jak najmniej.

Taki builder też dziwnie wygląda, dla mnie jest to wzorzec służący do szybkiego tworzenia skomplikowanych obiektów w prawidłowy sposób, czyli tak aby być pewnym, że mają ustawione wszystkie wymagane pola, a nie do robienia walidacji biznesowej i skomplikowanych interakcji z zewnętrznymi systemami. Builder to taki trochę mądrzejszy wrapper konstruktor/konstruktory.

Nie rozumiem, czemu po prostu nie przeprowadzić walidacji biznesowej w warstwie aplikacji, a encję utworzyć konstruktorem? Obecnie testowanie jednostkowe tej encji będzie wymagało mockowania, średnio to widzę.

4

Jak dla mnie to walidacja NIPu to powinno być tylko sprawdzenie, czy ilość cyfr się zgadza i czy cyfra kontrolna jest poprawna. A to, czy jest unikalny to chyba już jakaś operacja biznesowa, bo do sprawdzenia trzeba zaangażować więcej danych, niż tylko ten sprawdzany NIP.

0

Pojawiły się głosy, że nie powinno się wstrzykiwać serwisów serwisów odpytujących bazę danych do encji. Tylko że bez tego niestety dużo logiki biznesowej ucieka nam do warstwy aplikacji, a w samej encji zwykle niewiele zostaje. No chyba, że mamy tam jakieś złożone obliczenia albo walidacje, które da się przeprowadzić bez odpytywania infrastruktury.

Zakładałem kiedyś podobny temat, zostawię: http://www.kamilgrzybek.com/design/domain-model-validation/ (pozdrawiam @neves)

0

@somekind

Taki builder też dziwnie wygląda, dla mnie jest to wzorzec służący do szybkiego tworzenia skomplikowanych obiektów w prawidłowy sposób, czyli tak aby być pewnym, że mają ustawione wszystkie wymagane pola, a nie do robienia walidacji biznesowej i skomplikowanych interakcji z zewnętrznymi systemami. Builder to taki trochę mądrzejszy wrapper konstruktor/konstruktory.

Nie rozumiem, czemu po prostu nie przeprowadzić walidacji biznesowej w warstwie aplikacji, a encję utworzyć konstruktorem? Obecnie testowanie jednostkowe tej encji będzie wymagało mockowania, średnio to widzę.

Odejdźmy od encji, jest i załóżmy że jest to "system"

Chciałbym aby ten system potrafił się samemu wybronić przed wprowadzeniem go w zły stan

Mamy mnóstwo różnych klas ("systemów") w BCL .NETa, te klasy są używane przez ludzi którzy mają 5min jak i również 30 lat doświadczenia, one mają w sobie 5 metrów logiki "biznesowej" i potrafią się wybronić

Uważam że do tego powinno się dążyć, że nie tylko user ma ciężko coś popsuć (bo app layer zwaliduje), ale również inni pryncypale

@maszrum:

Jak dla mnie to walidacja NIPu to powinno być tylko sprawdzenie, czy ilość cyfr się zgadza i czy cyfra kontrolna jest poprawna. A to, czy jest unikalny to chyba już jakaś operacja biznesowa, bo do sprawdzenia trzeba zaangażować więcej danych, niż tylko ten sprawdzany NIP.

to tylko przykład, chodzi o to, że to coś musi być zwalidowane np. w bazce, external api, odczytane z arkusza excela wysłanego na maila :) - generalnie coś z zewnątrz.

5

Ten sposób wygląda dość przyzwoicie, ale bardzo utrudnia tworzenie obiektu, a w szczególności gdy on urośnie
oraz nadal ktoś może "wstrzyknąć" swój NIPValidator i popsuć.

To akurat jest błędne myślenie które chyba każdemu się zdarzyło na pewnym etapie kariery. Mianowicie próba zabezpieczenia danej klasy/właściwości/metody itp. przed wszystkim. W walidacji modelu i chronieniu zasad biznesowych nie chodzi o to żeby zabezpieczyć się przed programistom który może coś źle zrobić- tego nigdy się nie da w pełni zabezpieczyć. Od tego są peer reviews, rozmowy na temat zmian itp. Tak więc tak- w kodzie często będą miejsca gdzie programista teoretycznie mógłby zrobić coś nierozsądnego. To nijak ma się do walidacji modelu.

Jeśli chodzi o zagadnienie z perspektywy już typowo domenowej, to faktycznie od takich bardziej skomplikowanych walidacji są różne serwisy. Ewentualnie, jeśli chcesz być bardziej sprecyzowany co do tego że dana zmiana pola wymaga jakiejś walidacji, to stosując coś na wzór agregatów DDD powinieneś odwzorować operację zmiany wartości jako metodę która przyjmuje nową wartość jak i dodatkowy obiekt pomagający w walidacji. Wtedy jakiś serwis wyżej wyciąga z bazy/internetu co tam potrzeba do weryfikacji (np. jakieś konfigurowalne wartości) i przekazuje je do metody obiektu który chcesz zmienić. W tym przypadku oczywiście opieramy się na zasadzie zaufania że przekazane dane do metody są poprawne i można na nich polegać w kwestii walidacji. Mowa tutaj o przypadkach gdzie można coś wyegzekwować operując na danych w pamięci- jeśli musisz np. odpytać bazę o unikalność danych, to wtedy powinno to pozostać w gestii serwisu domenowego.

W przypadku C# to ja ogólnie jestem zwolennikiem stosowania properties z prywatnymi setterami, i dokonywania zmian poprzez metody, np:

class Something
{
  public string SometValue { get; private set; }

  public void ChangeSomeValue(string newValue, IEnumerable<string> validValuesSet)
  {
    // Logika biznesowa, walidacja itp.
    if (!validValuesSet.Contains(newValue)
      // nope

    SomeValue = newValue;
  }
}
1

W ramach ciekawostki - są różne rodzaje walidacji, okolice 22:00

0
WeiXiao napisał(a):

Odejdźmy od encji, jest i załóżmy że jest to "system"

Chciałbym aby ten system potrafił się samemu wybronić przed wprowadzeniem go w zły stan

System to zespół współpracujących ze sobą elementów, jak rozumiem Ty chcesz, aby wewnętrzny element robił to, co powinien robić cały system. Ergo, chcesz, aby ten wewnętrzny element był całym systemem, czyli aby system nie miał warstw, co musi skończyć się kodem spaghetti.

Mamy mnóstwo różnych klas ("systemów") w BCL .NETa, te klasy są używane przez ludzi którzy mają 5min jak i również 30 lat doświadczenia, one mają w sobie 5 metrów logiki "biznesowej" i potrafią się wybronić

Możesz podać przykład analogiczny do Twojej sytuacji?
BCL zazwyczaj jest zabezpieczony przed brakiem danych lub ich złym formatem - czyli tym samym, co powinna umieć logika biznesowa.

Uważam że do tego powinno się dążyć, że nie tylko user ma ciężko coś popsuć (bo app layer zwaliduje), ale również inni pryncypale

Jak najbardziej się zgadzam, ale jeśli ktoś ma dostęp do kodu, to te wszystkie zabezpieczenia i tak może usunąć. :)

to tylko przykład, chodzi o to, że to coś musi być zwalidowane np. w bazce, external api, odczytane z arkusza excela wysłanego na maila :) - generalnie coś z zewnątrz.

No i jaki powód, żeby nie zajęła się tym logika aplikacji?
To aplikacja jest systemem, to ona powinna być w stanie zapewnić spójność systemu, bo to komunikuje się z jego wejściami i wyjściami.

0

@Aventus:

public void ChangeSomeValue(string newValue, IEnumerable<string> validValuesSet)

A mógłbyś pokazać skąd biorą się te validation rules, kto je dostarcza?

jak to jest użyte od a do z

1

@WeiXiao: to już całkowicie zależy od konkretnego przypadku. Tak jak wspomniałem, mogą pochodzić z bazy danych systemu, lub z zewnętrznych serwisów. Dostarcza je serwis domenowy który wyciąga obiekt z repozytorium, i przekazuje tak jak w podanym przykładzie. Mogło by to wyglądać tak (bardzo abstrakcyjny przykład):

class ChangeSomeValueHandler : ICommandHandler<ChangeSomeValue>
{
  private readonly ISomethingRulesRetriever  _retriever;
  private readonly ISomethingRepository _repository;

  ctor(ISomethingRepository repository, ISomethingRulesRetriever retriever)
  {
    _repository = repository;
    _retriever = retriever;
  }

  public asyncTask Handle(ChangeSomeValue command)
  {
    // Pomijam tutaj null checks itp. żeby nie wprowadzać niepotrzebnego zamieszania w przykładzie
    var something = await _repository.Get(command.SomethingId);
    var validValues= await _retriever.Retrieve();
    
    something.ChangeSomeValue(command.NewValue, validValues);
  }
}
1

Temat mnie zaciekawił, trochę poeksperymentowałem. Coś mi wyszło, nie wiem na ile to jest poprawne dlatego proszę o uwagi, czy to w ogóle ma rację bytu. Linka do repo wrzucałem już wcześniej w komentarzu, ale zrobię to jeszcze raz w osobnym poście, żeby więcej osób zobaczyło.

Niestety bez kontenera DI chyba ciężko, bo nie wiadomo, jakie zależności będą chciały mieć reguły biznesowe. Ale najważniejsze co chciałem osiągnąć i udało się zrobić, to wszystkie reguły biznesowe znajdują się w warstwie domeny, a nie są wypchnięte do aplikacji. Przy tym encje domenowe nie mają żadnych zależności.

To wywoływanie metod na encjach wygląda trochę kulawo, ale nie wymyśliłem nic lepszego.

var result = await _domainInvoker.Do(
    () => companyOne.SetNip("7577261187"));

Tutaj przykładowy program, a tu test. Czy to ma jakiś sens?

0

@somekind:

Taki builder też dziwnie wygląda, dla mnie jest to wzorzec służący do szybkiego tworzenia skomplikowanych obiektów w prawidłowy sposób, czyli tak aby być pewnym, że mają ustawione wszystkie wymagane pola, a nie do robienia walidacji biznesowej i skomplikowanych interakcji z zewnętrznymi systemami.

Ale w sumie czemu się tak ograniczać?

1

Jeszcze odnośnie wstrzykiwania jakiegoś INipChecker do Company. Cytat z http://www.kamilgrzybek.com/design/domain-model-validation/:

Another argument for this option is that if I ever need to register Customer from a different use case I will not be able to bypass and forget about this uniqueness rule because I will have to pass this service

Do testów i tak pewnie będzie jakiś:

class FakeNipChecker
{
  private readonly List<Nip> _takenNips;

  public FakeNipChecker(List<Nip> takenNips = null)
  {
    _takenNips = takenNips ?? new List<Nip>();
  }

  public bool IsNipUnique(Nip nip) => !_takenNips.Contains(nip);
}

Konstruktora Company w testach pewnie i tak będziemy wywoływać bezpośrednio tylko, gdy będziemy chcieli przetestować tworzenie obiektu, w pozostałych przypadkach prawdopodobnie będziemy korzystali z jakiegoś buildera, który przekaże jakiegoś new FakeNipChecker() do konstruktora Company.

2
maszrum napisał(a):

Niestety bez kontenera DI chyba ciężko, bo nie wiadomo, jakie zależności będą chciały mieć reguły biznesowe.

Można wstrzyknąć zależności do serwisu, w nim utworzyć obiekt DomainIvoker z jawną listą reguł biznesowych. Zrzucanie tego na kontener IoC będzie nieczytelne i szybko nieużywalne, bo jest to spychanie logiki biznesowej do infrastruktury.
Jak obecnie zapewniasz wstrzykiwanie różnych implementacji reguł biznesowych do różnych serwisów?

Ale najważniejsze co chciałem osiągnąć i udało się zrobić, to wszystkie reguły biznesowe znajdują się w warstwie domeny, a nie są wypchnięte do aplikacji. Przy tym encje domenowe nie mają żadnych zależności.

To samo można osiągnąć implementując reguły biznesowe w serwisach domenowych, co do których chyba nikt nie ma wątpliwości, że mogą mieć zależności.

WeiXiao napisał(a):

Ale w sumie czemu się tak ograniczać?

Dlaczego builder poza budowaniem obiektu miałby też dociągać dane z zewnętrznych źródeł? To już wtedy nie będzie builder tylko fabryka.

0

@somekind:

Dlaczego builder poza budowaniem obiektu miałby też dociągać dane z zewnętrznych źródeł? To już wtedy nie będzie builder tylko fabryka.

to nie jest istotne, koncept pozostaje taki sam

1

Trochę nie rozumiem komplikowania sprawy poprzez wstrzykiwanie czegokolwiek do domenowego modelu.
Jeżeli tworzysz Nip to masz pole Nip, które zawiera w sobie odpowiednie reguły biznesowe (jakie mogą być znaki etc.). Wstrzykując bazę czy inne interfejsy i tak nie zapewnisz unikalności, bo ona jest zapewniona tylko w momencie wykonania zapytania do abzy i tak długo jak coś robisz potem, to jaką masz gwarancje, że w trym czasie akurat (pomiedzy zapytanie o unikalność a zapisem) ktoś nie dodał firmy z tym samym nipem? Jest to przedział kilku ms, ale jednak jest, czyli gwarancji nie masz.

Jeśli chcesz testować model, to powinien on mieć jak najmniej zależności a najlepiej wcale oraz udostepniać tylko niezbędne metody do spełnienia założeń biznesowych.

0

@kenik

Builder obiektu domenowego != obiekt domenowy.

Wstrzykujemy do buildera, nie obiektu.

bo ona jest zapewniona tylko w momencie wykonania zapytania do abzy i tak długo jak coś robisz potem, to jaką masz gwarancje, że w trym czasie akurat (pomiedzy zapytanie o unikalność a zapisem) ktoś nie dodał firmy z tym samym nipem? Jest to przedział kilku ms, ale jednak jest, czyli gwarancji nie masz.

Masz checki na różnych poziomach, to że sprawdzasz w momencie tworzenia obiektu nie oznacza że masz zrezygnować z checków na poziomie bazy.

I odpowiadając na pytanie po co:

A po co masz mieć możliwość tworzenia obiektu, który już w momencie próby jego utworzenia nie ma żadnego sensu?

Dzięki zabraniu możliwości tworzenia takich bezsensownych obiektów uważam że ograniczamy możliwość "psucia" rzeczy w systemie.

I nie jest to żaden nowy koncept - coraz więcej przerzucamy na typy. np. zapewnienie że coś nie jest NULLem, jest niezmienne czy ustawialne tylko podczas tworzenia obiektu, itd.

Im więcej takich "pewniaków", tym łatwiej jest robić rozkminy, bo nagle nie mamy pomysłów typu: a co jeżeli ktoś ustawił tu nulla, a co jeżeli ten email nie ma sensu, a co jeżeli te dane są niepoprawne.

1

Tańszym kosztem wydaje się stworzenie obiektu z duplikaten jakiegoś pola, niż strzelenie do bazy n razy (n walidacji innych pól -> mowa o najgorszym przypadku), zanim złapiemy ten błąd. Zgodnie z tym co mówisz, checki na bazie i tak masz to nic nie popsujesz.

I nie jest to żaden nowy koncept - coraz więcej przerzucamy na typy. np. zapewnienie że coś nie jest NULLem, jest niezmienne czy ustawialne tylko podczas tworzenia obiektu, itd.

Im więcej takich "pewniaków", tym łatwiej jest robić rozkminy, bo nagle nie mamy pomysłów typu: a co jeżeli ktoś ustawił tu nulla, a co jeżeli ten email nie ma sensu, a co jeżeli te dane są niepoprawne.

Jeżeli przejdzie walidację biznesową to zakładasz, że jest poprawny, więc tego typu rozkmin nie masz. A przy zapisie obiekt nie powinien się zapisać. Bo index czy jakaś reguła to zablokuje. A model powinien mieć wystarczające informacje by stwierdzić czy można stworzyć czy nie. Jeżeli dane przychodzą od Ciebie to zawsze zakłądasz, że to pewniak, ianczej byś musiał przeprowadzać walidację co chwila.
U mnie zawsze obowiązywała reguła, że ograniczamy roundtripy do bazy czy jakieś operacje I/O do minimum.

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