Z mojego doświadczenia, sporo problemów powodują ogólnie filtry doctrinowe. Przy softdelete trzeba dodawać, fetch eager do relacji. Gdy musiałem dobrać się do usuniętych obiektów to nie jednokrotnie filtr trzeba było wyłączać, a co gorsze po masie bugów jakie mnie przez to dopadły konieczne było każdorazowe włączenie filtra. W małych e-commercach do archiwizowania np. produktów w zamówieniu korzystałem z serializacji i zapisu w osobnym polu/encji trochę pracy to wymagało bo trzeba było nadpisywać serializowanie ale mnie jeszcze to nie zawiodło i nie znalazłem na tą chwilę lepszego rozwiązania, zawsze można było zapisać na boku cenę i nazwę ale wygodniej pracuje się na całym obiekcie, choćby częściowo wypełnionym.
Jasne. Chociaż być może właśnie obiekt może zostać oznaczony jak zarchiwizowany i to jest sensowne rozwiązanie bo tak wychodzi z domeny. Co do zapisu obiektu zserializowanego to rozwiązanie może rodzić problemy jeśli jakieś pola w obiektach się pozmieniają (dodamy coś, edytujemy itp). To okaże się że ciężko się migruje tak zapisane obiekty - ale fakt jest to wygodne. W każdym razie soft-deletowanie wszystkiego co się da z natury jest słabym pomysłem - przynajmniej według mnie.
świetny film, można sie sporo ciekawostek dowiedzieć. Jednakże mam coś do dodania. Zauważyłem, że w takich filmach często prowadzący używają słów, które określają jakąś czynnośc. Np: u Ciebie pojawiło się hydrowanie (o ile dobrze zrozumiałem wymowę). Nie znałem go wcześniej, jednakże z kontekstu można domyśleć się, że chodzi o ładowanie obiektów z bazy (popraw mnie jeśli się mylę.). Proponuje dodawać na ekranie w momencie kiedy pierwszy raz pojawia się na filmie jego wyjaśnienie. Ewentualnie dodać to do opisu filmu. A druga sprawa czy planujesz zrobić film na temat DDD lub architektury hexagonalnej? Ciężko znaleźć dobry materiał po polsku. Przerobiłem kilka zagranicznych ale zawsze warto byłoby potwierdzić wiedzę, z lokalnego podwórka. PS. więcej przykładowego kodu proszę! np. w N+1 mogłeś pokazać jak to wszystko wygląda od strony kodu i repo daj znać, że odczytałeś :))
Dziękuje za podzielenie się opiniami. Co do używanych słów to zawsze jest problem, ciężko tłumaczyć wszystko. Sam czasem oglądam coś i nie rozumiem jakiegoś słowa ale wtedy we własnym zakresie szukam w internecie znaczenia. Co do hydracji upraszczając dobrze zrozumiałeś. Na razie dotykam konceptów z DDD tylko w wąskim zakresie, który można przenieść do kodu nawet nie wykorzystując w pełni DDD, na przykład Encja czy Repozytorium. Samego DDD dopiero się uczę i nie czuję żebym był kompetentny do szkolenia z tego. Polecam Ci na przykład posłuchać podcastu Better Software Design. Tam znajdziesz kilka ciekawych materiałów. Co do architektury hexagonalnej na razie nie mam w planach, ale gdybym kiedyś jakiś projekt pokazowy tworzył to pomyślałbym czy nie wpleść tego dodatkowo. Kod ciężko pokazywać w takich prezentacjach, to raczej teoria. 🙂
Co do UUID, nie wspomniales o minusach a jest nim miedzy innymi nierownomierny rozklad wartosci co w polaczeniu z tym jak dziala mechanizm cachowania w MySQL (InnoDB), uzycie takiej wartosci na kluczach glownych spowoduje masakrycznie slaba wydajnosc juz przy stosunkowo niewielkich zbiorach danych.
To prawda, bo nie jestem ekspertem w temacie baz danych, a temat nie jest taki zerojedynkowy. Z tego co kojarzę to faktycznie może zadziałać to słabo na wydajność przy UUID jako klucz główny, ale są sposoby jak tego UUID używać dobrze, tak by wydajność nie spadła. I też bym zdefiniował co masz na myśli mówiąc niewielkie zbiory.
@@Koddlo Niewielkie (to z praktycznego przykladu) 200k rekordow i tabela laczaca 3 encje po uuid. Ten sam serwer nie mial problemu z obsluzeniem tabel po 45m rekordow, laczonych w tradycyjny sposob. Binary pomaga ale nieznacznie ze wzgledu na specyfike cache w InnoDB (w High Performace MySQL jest caly rozdzial na ten temat)
Wtedy to nie jest UUID. Jest to ID. Oczywiście może być, jeśli masz taką potrzebę. Niestety musisz wówczas gdzieś zapisywać informację o najwyższym ID albo pytać bazę w momencie tworzenia obiektu, co niestety może powodować też problem tego typu, że stworzysz kilka obiektów z tym samym identyfikatorem. UUID daje Ci unikalność na poziomie tabeli i całej bazy. ID, które opisałeś tylko na poziomie tabeli.
Bardzo interesujący materiał. Kiedyś gdy czytałem o miękkim usuwaniu, było napisane że każda informacja jest cenna i zachęcali do miękkiego usuwania. A jeśli chodzi o takie zapytania, można mieć kolumnę w bazie "statusSoftDelete" lub coś takiego? W doctrine nie ma na wbudowanych metodach findAll, findOne lub find, findById parametrów typu findAll([ "where" => ["statusSoftDelete => false]] dla bardzo prostych zapytań i trzeba wtedy własne pisać? Pokażesz w przyszłości jak odbierać dane z frontu w formacie json gdy piszemy np. w Symfony REST API? Chodzi mi o najbardziej polecany sposób i jak takie dane przechowywać gdy w jsonie przyjdzie obiekt który ma inne obiekty i tablice, jak się takie rzeczy trzyma w relacyjnych bazach, bo jeśli chodzi o mongoDB nie ma z tym problemu (wszystko w składni jsowej więc łatwo)?
Dzięki za komentarz. To po kolei: - można mieć takie pole (ale jeśli wsłuchasz się w to o czym mówiłem w wielu przypadkach okaże się, że nie powinno go tam być). - można skorzystać z wbudowanej metody findBy() do której przekażesz właśnie parametry dla klauzuli WHERE. Jednakże ja uważam, że lepiej jest robić swoje własne w każdym przypadku - o czym mówię w prezentacji i też podaję argumenty dlaczego. - kto wie, może i na taki materiał przyjdzie pora. Co do odbierania danych w jsonie i tak jeśli trzyma się to w relacyjnej bazie danych pisze się jakieś normalizery/serializery, które tego jsona przekształcą na faktyczny obiekt, ale oczywiście to nie jedyne rozwiązanie. Da się przetrzymywać jsony też w bazach relacyjnych, ale ma to swoje zalety i wady - dużą rolę odgrywa tutaj też silnik bazodanowy. W sieci na pewno znajdziesz o tym sporo materiałów, a może i ja coś kiedyś takiego przygotuję.
Jeśli już koniecznie potrzebujemy używać soft-delete to chciałbym dorzucić dwie rady - pierwsza, żeby zamiast flagi trzymać datę usunięcia, np. deletedAt - dzięki temu będziemy mieć informację czy encja została usunięta (jeśli nie deletedAt będzie puste) oraz kiedy. Druga rada dotyczy tego, że istnieje sposób na to aby rozszerzyć Doctrine o globalne filtry, które będą aplikowane zawsze i przez to będzie można wrzucić tam przykładowy where sprawdzający naszą datę w każdym zapytaniu. Niestety trzeba do tego napisać własny sqlWalker i wpiąć go przez hint w entityManagerze. www.doctrine-project.org/projects/doctrine-orm/en/2.7/cookbook/dql-custom-walkers.html
Skoro są potrzebne ale usunięte i nie można ich zmienić (bo przecież już są usunięte) to lepiej zrobić identyczną tabelę np. z suffixem archive, która nie ma relacji. Unią można wyciągnąć dane z oby dwóch tabel. Opcjonalnie do tabeli _archive tylko dodawanie i odczyt bez modyfikacji.
@@hubert7855 Twoje rozwiązanie jest interesujące, ale jeśli chciałbyś to zamodelować obiektowo to podczas operacji usuwania musielibyśmy utworzyć dodatkową encję i przekopiować właściwości z tej usuwanej do tej archiwalnej. Podobnie wyciąganie takich rekordów przez jakiegoś ORMa może być bardziej skomplikowane niż prostej kolekcji. Jest to bardziej skomplikowane niż ustawienie flagi, a jeśli chodzi o ilość danych to jest bez zmian w stosunku do soft-delete. Gdybym miał zrobić sam changelog do przeglądania informacji co stało się w systemie to raczej utworzyłbym jedną tabelę zawierającą eventy i dane przekształcanych obiektów w jakimś jsonie. Może się mylę?
Źle się wyraziłem albo Ty źle zrozumiałeś. W ogromnych bazach raczej nie będzie używany ze względu na wydajność. Tam już jakiś inny read model zapewne będzie.
1. Robi COUNT z użyciem DISTINCT. 2. Podzapytanie z LIMIT, żeby znaleźć wszystkie ID dla aktualnej strony. 3. Zapytanie z WHERE IN i wrzuca tam wcześniej znalezione ID dla aktualnej strony.
świetny materiał!
Z mojego doświadczenia, sporo problemów powodują ogólnie filtry doctrinowe. Przy softdelete trzeba dodawać, fetch eager do relacji. Gdy musiałem dobrać się do usuniętych obiektów to nie jednokrotnie filtr trzeba było wyłączać, a co gorsze po masie bugów jakie mnie przez to dopadły konieczne było każdorazowe włączenie filtra.
W małych e-commercach do archiwizowania np. produktów w zamówieniu korzystałem z serializacji i zapisu w osobnym polu/encji trochę pracy to wymagało bo trzeba było nadpisywać serializowanie ale mnie jeszcze to nie zawiodło i nie znalazłem na tą chwilę lepszego rozwiązania, zawsze można było zapisać na boku cenę i nazwę ale wygodniej pracuje się na całym obiekcie, choćby częściowo wypełnionym.
Jasne. Chociaż być może właśnie obiekt może zostać oznaczony jak zarchiwizowany i to jest sensowne rozwiązanie bo tak wychodzi z domeny. Co do zapisu obiektu zserializowanego to rozwiązanie może rodzić problemy jeśli jakieś pola w obiektach się pozmieniają (dodamy coś, edytujemy itp). To okaże się że ciężko się migruje tak zapisane obiekty - ale fakt jest to wygodne. W każdym razie soft-deletowanie wszystkiego co się da z natury jest słabym pomysłem - przynajmniej według mnie.
świetny film, można sie sporo ciekawostek dowiedzieć.
Jednakże mam coś do dodania. Zauważyłem, że w takich filmach często prowadzący używają słów, które określają jakąś czynnośc. Np: u Ciebie pojawiło się hydrowanie (o ile dobrze zrozumiałem wymowę). Nie znałem go wcześniej, jednakże z kontekstu można domyśleć się, że chodzi o ładowanie obiektów z bazy (popraw mnie jeśli się mylę.).
Proponuje dodawać na ekranie w momencie kiedy pierwszy raz pojawia się na filmie jego wyjaśnienie.
Ewentualnie dodać to do opisu filmu.
A druga sprawa czy planujesz zrobić film na temat DDD lub architektury hexagonalnej? Ciężko znaleźć dobry materiał po polsku. Przerobiłem kilka zagranicznych ale zawsze warto byłoby potwierdzić wiedzę, z lokalnego podwórka.
PS. więcej przykładowego kodu proszę! np. w N+1 mogłeś pokazać jak to wszystko wygląda od strony kodu i repo
daj znać, że odczytałeś :))
Dziękuje za podzielenie się opiniami.
Co do używanych słów to zawsze jest problem, ciężko tłumaczyć wszystko. Sam czasem oglądam coś i nie rozumiem jakiegoś słowa ale wtedy we własnym zakresie szukam w internecie znaczenia. Co do hydracji upraszczając dobrze zrozumiałeś.
Na razie dotykam konceptów z DDD tylko w wąskim zakresie, który można przenieść do kodu nawet nie wykorzystując w pełni DDD, na przykład Encja czy Repozytorium. Samego DDD dopiero się uczę i nie czuję żebym był kompetentny do szkolenia z tego. Polecam Ci na przykład posłuchać podcastu Better Software Design. Tam znajdziesz kilka ciekawych materiałów. Co do architektury hexagonalnej na razie nie mam w planach, ale gdybym kiedyś jakiś projekt pokazowy tworzył to pomyślałbym czy nie wpleść tego dodatkowo.
Kod ciężko pokazywać w takich prezentacjach, to raczej teoria. 🙂
Co do UUID, nie wspomniales o minusach a jest nim miedzy innymi nierownomierny rozklad wartosci co w polaczeniu z tym jak dziala mechanizm cachowania w MySQL (InnoDB), uzycie takiej wartosci na kluczach glownych spowoduje masakrycznie slaba wydajnosc juz przy stosunkowo niewielkich zbiorach danych.
To prawda, bo nie jestem ekspertem w temacie baz danych, a temat nie jest taki zerojedynkowy. Z tego co kojarzę to faktycznie może zadziałać to słabo na wydajność przy UUID jako klucz główny, ale są sposoby jak tego UUID używać dobrze, tak by wydajność nie spadła. I też bym zdefiniował co masz na myśli mówiąc niewielkie zbiory.
Z tego co wiem to binary UUID działa optymalnie.
@@Koddlo Niewielkie (to z praktycznego przykladu) 200k rekordow i tabela laczaca 3 encje po uuid. Ten sam serwer nie mial problemu z obsluzeniem tabel po 45m rekordow, laczonych w tradycyjny sposob.
Binary pomaga ale nieznacznie ze wzgledu na specyfike cache w InnoDB (w High Performace MySQL jest caly rozdzial na ten temat)
Wtedy to nie jest UUID. Jest to ID. Oczywiście może być, jeśli masz taką potrzebę. Niestety musisz wówczas gdzieś zapisywać informację o najwyższym ID albo pytać bazę w momencie tworzenia obiektu, co niestety może powodować też problem tego typu, że stworzysz kilka obiektów z tym samym identyfikatorem. UUID daje Ci unikalność na poziomie tabeli i całej bazy. ID, które opisałeś tylko na poziomie tabeli.
Bardzo interesujący materiał. Kiedyś gdy czytałem o miękkim usuwaniu, było napisane że każda informacja jest cenna i zachęcali do miękkiego usuwania. A jeśli chodzi o takie zapytania, można mieć kolumnę w bazie "statusSoftDelete" lub coś takiego? W doctrine nie ma na wbudowanych metodach findAll, findOne lub find, findById parametrów typu findAll([ "where" => ["statusSoftDelete => false]] dla bardzo prostych zapytań i trzeba wtedy własne pisać?
Pokażesz w przyszłości jak odbierać dane z frontu w formacie json gdy piszemy np. w Symfony REST API? Chodzi mi o najbardziej polecany sposób i jak takie dane przechowywać gdy w jsonie przyjdzie obiekt który ma inne obiekty i tablice, jak się takie rzeczy trzyma w relacyjnych bazach, bo jeśli chodzi o mongoDB nie ma z tym problemu (wszystko w składni jsowej więc łatwo)?
Dzięki za komentarz. To po kolei:
- można mieć takie pole (ale jeśli wsłuchasz się w to o czym mówiłem w wielu przypadkach okaże się, że nie powinno go tam być).
- można skorzystać z wbudowanej metody findBy() do której przekażesz właśnie parametry dla klauzuli WHERE. Jednakże ja uważam, że lepiej jest robić swoje własne w każdym przypadku - o czym mówię w prezentacji i też podaję argumenty dlaczego.
- kto wie, może i na taki materiał przyjdzie pora. Co do odbierania danych w jsonie i tak jeśli trzyma się to w relacyjnej bazie danych pisze się jakieś normalizery/serializery, które tego jsona przekształcą na faktyczny obiekt, ale oczywiście to nie jedyne rozwiązanie. Da się przetrzymywać jsony też w bazach relacyjnych, ale ma to swoje zalety i wady - dużą rolę odgrywa tutaj też silnik bazodanowy. W sieci na pewno znajdziesz o tym sporo materiałów, a może i ja coś kiedyś takiego przygotuję.
Jeśli już koniecznie potrzebujemy używać soft-delete to chciałbym dorzucić dwie rady - pierwsza, żeby zamiast flagi trzymać datę usunięcia, np. deletedAt - dzięki temu będziemy mieć informację czy encja została usunięta (jeśli nie deletedAt będzie puste) oraz kiedy. Druga rada dotyczy tego, że istnieje sposób na to aby rozszerzyć Doctrine o globalne filtry, które będą aplikowane zawsze i przez to będzie można wrzucić tam przykładowy where sprawdzający naszą datę w każdym zapytaniu. Niestety trzeba do tego napisać własny sqlWalker i wpiąć go przez hint w entityManagerze.
www.doctrine-project.org/projects/doctrine-orm/en/2.7/cookbook/dql-custom-walkers.html
Skoro są potrzebne ale usunięte i nie można ich zmienić (bo przecież już są usunięte) to lepiej zrobić identyczną tabelę np. z suffixem archive, która nie ma relacji.
Unią można wyciągnąć dane z oby dwóch tabel. Opcjonalnie do tabeli _archive tylko dodawanie i odczyt bez modyfikacji.
@@hubert7855 Twoje rozwiązanie jest interesujące, ale jeśli chciałbyś to zamodelować obiektowo to podczas operacji usuwania musielibyśmy utworzyć dodatkową encję i przekopiować właściwości z tej usuwanej do tej archiwalnej. Podobnie wyciąganie takich rekordów przez jakiegoś ORMa może być bardziej skomplikowane niż prostej kolekcji. Jest to bardziej skomplikowane niż ustawienie flagi, a jeśli chodzi o ilość danych to jest bez zmian w stosunku do soft-delete. Gdybym miał zrobić sam changelog do przeglądania informacji co stało się w systemie to raczej utworzyłbym jedną tabelę zawierającą eventy i dane przekształcanych obiektów w jakimś jsonie. Może się mylę?
@@jakubksiazek4740 dokładnie, kopiujesz na event usunięcia encje np Order do OrderArchived
Paginatora nie używamy do dużych zbiorów danych? Ciekawa teza... chyba właśnie głównie tam...
Źle się wyraziłem albo Ty źle zrozumiałeś. W ogromnych bazach raczej nie będzie używany ze względu na wydajność. Tam już jakiś inny read model zapewne będzie.
To jak on działa? Nie robi po prostu SQLowego "LIMIT" w zapytaniu, tylko pobiera całość i paginuje na kolekcji?
1. Robi COUNT z użyciem DISTINCT.
2. Podzapytanie z LIMIT, żeby znaleźć wszystkie ID dla aktualnej strony.
3. Zapytanie z WHERE IN i wrzuca tam wcześniej znalezione ID dla aktualnej strony.