U mnie encja biznesowa to np. User i tworząc Usera nie widzę problemu w tym, że będzie tam 10 parametrów w konstruktorze - używasz tutaj słowa zależność.
Ja tu widzę mega dużo problemów. 10 parametrów, potencjalnie 10 powodów do zmiany. Różne permutacje tych parametrów daje nam 10^n
gdzie n
to ilość możliwych stanów, tysiące kombinacji. Klasa bardzo trudna do zrozumienia i utrzymania.
W domenie biznesowej, moim zdaniem nieakceptowalne.
Możesz nałożyć na to buildery i inne pomagajki, ale to będzie tylko ukrycie skomplikowanej klasy.
Dla mnie zależność to zależność od jakiegoś serwisu, ale Ty raczej traktujesz to szerzej i np. przyjęcie stringa jako email też traktujesz jako zależność. Także dla mnie tworzenie encji biznesowych przyjmujących dane czy to jako skalary czy ValueObjects (a nie zależności w moim rozumieniu, czyli jakieś inne Servisy) to jest całkiem rozsądna praktyka.
Wszystko jedno co. Jeśli musisz to znać w teście, nie ważne czy to service czy email, musisz to znać w teście, ergo musisz to dostarczyć w teście, ergo Twoja klasa nie może bez tego działać, ergo to zależność. Nazywaj sobie to jak chcesz.
Nie zdarzyło mi się jeszcze tworzyć encji biznesowej ze 100 polami - takie zwyczajnie rozbijam bo np. w Userze adress to już będzie ValueObject albo jakaś inna Encja. Także w praktyce limitem jest tutaj po prostu zdrowy rozsądek.
No widzisz, a mi się nie zdarzyło nigdy stworzyć encji która ma 7 pól. Większość u mnie ma 1-3.
Patrzę na Twoje argumenty z tym że masz 10 parametrów, tak jak Ty byś popatrzył na kogoś kto ma ich 100.
W przypadku obiektów przyjmujących zależności w moim rozumieniu (czyli np. coś wstrzykniętego przez DI) to w 100% się zgadzam - im mniej tym lepiej, ale w przypadku DTO nie widzę powodu czemu miałbym się ograniczać do np. 3 parametrów w konstruktorze.
W DTO nie musisz. Ale potem musisz je przetłumaczyć tak, żeby dało się ich użyć żeby zawołać logikę biznesową, i to już nie może ich mieć dużo.
Idąc od środka w domenie mam encję biznesową User, która w konstruktorze przyjmuje wszystkie parametry jakie powinien mieć user - najczęściej w postaci ValueObjectów typu UserAddress, Password itp. Zazwyczaj nie ma ich wiele, ale ~10 się zdarza.
No, moim zdaniem 10 to dużo za dużo jak mówiłem. Dla mnie spoko to 1-3, przy 4-5 bym się zastanawiał czy by tego nie podzielić, przy 6 na 100% bym je już wydzielił. Nigdy nie doprowadziłbym do sytuacji w której jest ich 10. Dla mnie to za dużo, dlatego że prowadzi DOKŁADNIE do takich problemów jakie masz, czyli buildery w testach, które są takim pain in the ass że musisz je generować.
Dalej ma Service Domenoy w postaci UseCase - w tym przypadku RegisterUserUseCase - do tego UseCasu mam obiekt typu Command - np. RegisterUserCommand, który niektórzy nazywają Requestem (ale nie w rozumieniu Requesta Http tylko requestu UseCasu).
Idąć wyżej - mam Adaptery np. dla HTTP i CLI, ich odpowiedzialnością jest przetłumaczenie natywnego Requestu np. Http na Command jaki przyjmuje dany Use Case -> czyli w tym przypadku kontroller tworzy DTO jakim jest UserRegisterCommand i wywoluje RegisterUserUseCase. W odpowiedzi Use Case zwraca RegisterUserRespone (czyli znowu jakieś DTO), które jest tłumaczone na odpowiedni Response HTTP. Ten sam Use Case używam w adapterze CLI. Robiąc testy domeny zazwyczaj punktem wejścia jest właśnie ten DTO, który normalnie jest tworzony w kontolerze albo komendzie CLI.
Zawsze uważałem, że to podejście jest właśnie prawidłowe bo ładnie odwiązuje logikę domenową od infrastruktury i poszczególnych adapterów. Oczywiście celowo pomijam tutaj detale typu używanie interejsów dla odwórcena zależności itp, ale to już taki detal. Wg mnie to całkiem solidna architektura.
Tak, zmiana requestu HTTP na RequestModel, czy tam jak sobie nazwałeś RequestUseCase, czy DTO, to jest spoko pomysł. Ale to tylko pierwszy krok. Jeszcze musisz użyć tego DTO, żeby zawołać logikę biznesową tak żeby ona nie wiedziała o tych DTO nic.
I tutaj szczerze mówiąc nie wiem jak uzyskać Command, który zawsze miałbym 2-3 pola w konstruktorze. Jeśli np. jesteśmy w kontekście http i user wysyła formularz z 10 polami koniecznymi do rejestracji usera, to Command też w przybliżeniu będzie miał taką ilość.
No zależy czy te dane wszystkie są używane do tego samego (np wszystko to jest adres do przesyłki, kraj, kod pocztowy, ulica, adres, numer mieszkania), czy każdy do czegoś innego (login do logowania, mail do powiadomień, numer telefonu do weryfikacji, imię do wyświetlania, wiek do cenzury, etc.).
Jeśli wszystko jest do jednego celu (co jest raczej rzadkie), to wsadzasz to do klasy Address
, i wtedy Twoja command ma jeden argument - Address $address
. Zapytasz pewnie jak to przetestować - otóż tak żę przekazujesz do Command
w teśćie implementację Address która robi to co ma robić Fake/Mock/inna implementacja/implementacja z defaultami. Ale do tego nigdy nie potrzebujesz builderów bo to są gołe dane, a nie enjce. Tak na prawdę mógłbyś to wręcz trzymać w array
.
Jeśli natomiast różne pola robią różne rzeczy, to tak na prawdę to co nazywasz "jedną Command
" tak na prawdę jest wieloma commandami, które robią wiele rzeczy, i powinieneś je rozdzielić IMO.
Jak testuje w danym teście jak wpływa wiek usera na proces rejestracji,
...taaak, ale w połączeniu z 14oma innymi implicit parametrami. Więc tak na prawdę to ja nie byłbym taki pewien czy ten test testuje jedną rzeczy czy nie. Moim zdaniem chyba nie.
bo nie widzę powodu dla którego miałbym tworzyć cały Command ręcznie.
Gdyby byl prosty, to byłoby to banalne, np tak new Command(a,b,c)
. To że u Ciebie to wygląda tak new Command(a,b,c,d,e,f,g,h,i)
to moim zdaniem źle zaprojektowane API.
Dla mnie ważne jest aby w kontekście tego testu wszystkie pozostałe dane spełniały reguły biznesowe dla prawidłowego wywołania dane Use Casa aby nie dostać false positive, a wiek jest tym co w danym teście wpływa na SUT.
Nie widzę powodu czemu np. miałbym w 30 testach zmieniać command dodając mu to dodatkowe pole, które nie wpływa na dany test.
Ale ono już by było dodane czy tego chcesz czy nie. Pytanie tylko czy byłoby dodane jawnie (lepiej), czy nie jawnie, za pośrednictwem tego builder.
Oczywiście dodając takie pole jak najbardziej trzeba przejrzeć testy, ale tym bardziej będzie łatwiej wychwycić test gdzie manipuluję polem "city" i tam w pierwszej kolejności patrzeć czy dodanie pola "country" nie zmienia reguł biznesowych.
To jest tylko jeden i prosty przypadek. Są inne case'y, np test shouldNotDoSomethingWhenNoCityIsSet
, i tam już nie będzie tego withCity()
więc nie znajdziesz tego tak łatwo.
Ale to co naprawdę byłoby łatwo znaleźć, to nie 30 testów Commanda, tylko 3 testy klasy która ma 3 pola, zamiast 10-15. Gdyby wszystkie Twoje klasy miały 1-3 zależności, to Twoje testy byłby dużo mniejsze i prostsze, nie byłoby takiego problemu jak "znaleźć test", nie musiałyś ich szukać, ponieważ byłby tak proste, że ich intencje byłyby oczywiste. Mając klasę która ma 10-12 parametrów, nie ma mowy o czystych i prostych testach. Owszem, możesz sobie je próbować schować w builder, ale to nie sprawi że będzie ich prościej szukać/zmieniać/rozwijać/edytować. Po pierwsze teraz testy są zależne od buildera, oraz są nie bezpośrednio związane również ze złamaną enkapsulacją.
Czy na prawdę nie widzisz tego, że klasa która ma 1-3 argumenty MUSI być prosta i łatwa (zakładając żę jesteś dobrym programistom)? Nigdy nie będziesz potrzebował buildera ani innych pomagajek. Złamać SRP jest znacznie ciężej, Twoja architektura sama się zrobi. Przekonanie że 10 parametrów w klasie jest "czasem okej" to jest Twój największy problem IMO. Moim zdaniem to jest zawsze problem (w domenie bizesowej, w DTO i requestach jest ok).
Pracowałem kiedyś w taki sposób w jaki opisujesz, więc wydaje mi się że wiem co myślisz - wydaje mi się że wiem, bo kiedyś myślałem podobnie. Dużo czasu zajęło mi wyrobienie na tyle dyscypliny żeby umieć się przed tym zabezpieczyć i umieć zrobić dobre aplikacje.
Mam wrażenie że schodzisz na dziwny temat.
- Najpierw rozmowa zaczęła się od tego, że moim zdaniem generatory ukrywają skomplikowane API.
- Ty dałeś kontr argument z builderami
- Ja powiedziałem żę z reguł buildery chowają skomplikowane API, i builder to tylko maskowanie ich
- Teraz Ty dajesz przykład z Commandem który wchodzi do Twojej logiki biznesowej z 10 parametrami.
Czyli mam rozumieć że tamte tematy mamy załatwione?