Jak stworzyć lepszy kod OO w aplikacji opartej na relacyjnej bazie danych, w której baza danych jest źle zaprojektowana

19

Piszę aplikację internetową Java, która składa się głównie z kilku podobnych stron, na których każda strona ma kilka tabel i filtr, który dotyczy tych tabel. Dane w tych tabelach pochodzą z bazy danych SQL.

Używam myBatis jako ORM, co może nie być najlepszym wyborem w moim przypadku, ponieważ baza danych jest źle zaprojektowana, a mybatis jest narzędziem bardziej zorientowanym na bazę danych.

Stwierdzam, że piszę dużo zduplikowanego kodu, ponieważ z powodu złego projektu bazy danych muszę pisać różne zapytania dotyczące podobnych rzeczy, ponieważ mogą one być bardzo różne. Oznacza to, że nie mogę łatwo sparametryzować zapytań. To propaguje się do mojego kodu i zamiast wypełniać wiersze w kolumnach w mojej tabeli za pomocą prostej pętli mam kod taki jak:

dostać danych (p1, ..., pi);

pobierz dane B (p1, ..., pi);

pobierz dane C (p1, ..., pi);

pobierz D Data (p1, ..., pi); ...

I to wkrótce eksploduje, gdy mamy różne tabele z różnymi kolumnami.

Dodaje również do złożoności fakt, że używam „furtki”, która w rzeczywistości polega na mapowaniu obiektów na elementy HTML na stronie. Tak więc mój kod Java staje się adapterem między bazą danych a interfejsem, co powoduje, że tworzę dużo okablowania, bojlerowego kodu z pewną logiką.

Czy poprawnym rozwiązaniem byłoby owijanie maperów ORM dodatkową warstwą, która ma bardziej jednorodny interfejs do bazy danych, czy też istnieje lepszy sposób radzenia sobie z tym kodem spaghetti, który piszę?

EDYCJA: Więcej informacji o bazie danych

Baza danych zawiera głównie informacje o połączeniach telefonicznych. Słaba konstrukcja składa się z:

Tabele ze sztucznym identyfikatorem jako kluczem podstawowym, który nie ma nic wspólnego ze znajomością domeny.

Żadnych unikalnych, wyzwalaczy, czeków ani obcych kluczy.

Pola o nazwie ogólnej, które pasują do różnych pojęć dla różnych rekordów.

Rekordy, które można kategoryzować tylko poprzez krzyżowanie z innymi tabelami o różnych warunkach.

Kolumny, które powinny być liczbami lub datami zapisanymi jako ciągi.

Podsumowując, bałagan / leniwy projekt dookoła.

DPM
źródło
7
Czy poprawianie projektu bazy danych jest opcją?
RMalke
1
Proszę wyjaśnić, w jaki sposób baza danych jest źle zaprojektowana.
Tulains Córdova
@Renan Malke Stigliani Niestety nie, ponieważ istnieje starsze oprogramowanie, które od niego zależy, jednak dublowałem niektóre tabele o nieco innym projekcie i wypełniłem je, co upraszcza kod. Jednak nie jestem z tego dumny i wolałbym nie duplikować tabel bez rozróżnienia
DPM
1
Ta książka może ci pomóc w rozwiązaniu problemu z bazą danych i utrzymaniu działania starszego kodu: amazon.com/…
HLGEM
4
Większość wymienionych problemów. . . nie są. Użycie kluczy zastępczych zamiast kluczy naturalnych jest obecnie dość standardową rekomendacją; wcale nie „kiepski projekt”. Brak ograniczeń i użycie nieodpowiednich typów kolumn jest lepszym przykładem, jeśli chodzi o „słaby projekt”, ale nie powinno to w ogóle wpływać na kod aplikacji (chyba że planujesz nadużywanie tych problemów?).
ruakh

Odpowiedzi:

53

Orientacja obiektowa jest szczególnie cenna, ponieważ powstają tego rodzaju scenariusze i daje narzędzia do racjonalnego projektowania abstrakcji, które pozwalają na zawarcie złożoności.

Prawdziwe pytanie brzmi: gdzie ujmujesz tę złożoność?

Pozwólcie, że cofnę się na chwilę i porozmawiam o tym, o której „złożoności” mówię tutaj. Twój problem (jak rozumiem; popraw mnie, jeśli się mylę) to model trwałości, który nie jest efektywnie użytecznym modelem do zadań, które musisz wykonać z danymi. Może być skuteczny i użyteczny do innych zadań, ale nie do twoich zadań.

Co więc robimy, gdy mamy dane, które nie przedstawiają dobrego modelu dla naszych środków?

Tłumaczyć. To jedyne, co możesz zrobić. To tłumaczenie jest „złożonością”, o której mowa powyżej. Teraz, gdy akceptujemy zamiar przetłumaczenia modelu, musimy zdecydować o kilku czynnikach.

Czy musimy tłumaczyć oba kierunki? Czy oba kierunki będą tłumaczone tak samo, jak w:

(Tbl A, Tbl B) -> Obj X (czytaj)

Obj X -> (Tbl A, Tbl B) (zapis)

czy też czynności wstawiania / aktualizacji / usuwania reprezentują inny typ obiektu, tak że odczytujesz dane jako Obj X, ale dane są wstawiane / aktualizowane z Obj Y? Który z tych dwóch sposobów chcesz przejść, lub jeśli nie jest możliwa aktualizacja / wstawianie / usuwanie, to ważne czynniki, w których chcesz umieścić tłumaczenie.


Gdzie tłumaczysz

Powrót do pierwszego stwierdzenia, które wypowiedziałem w tej odpowiedzi; OO pozwala ci na hermetyzację złożoności, a to, o czym tu mówię, to fakt, że nie tylko powinieneś, ale musisz hermetyzować tę złożoność, jeśli chcesz mieć pewność, że nie wycieknie ona i nie dostanie się do całego twojego kodu. Jednocześnie ważne jest, aby zdać sobie sprawę, że nie możesz mieć idealnej abstrakcji, więc martw się o to bardziej niż o bardzo skuteczną i użyteczną.

Znowu teraz; Twoim problemem jest: gdzie stawiasz tę złożoność? Masz wybór.

Możesz to zrobić w bazie danych za pomocą procedur przechowywanych. Ma to tę wadę, że często nie gra zbyt dobrze z ORM, ale nie zawsze tak jest. Procedury przechowywane zapewniają pewne korzyści, w tym często wydajność. Procedury przechowywane mogą jednak wymagać wielu czynności konserwacyjnych, ale to do Ciebie należy przeanalizowanie konkretnego scenariusza i stwierdzenie, czy konserwacja będzie większa czy mniejsza niż inne opcje. Ja osobiście mam duże umiejętności w zakresie procedur przechowywanych i jako taki fakt dostępnych talentów zmniejsza koszty ogólne; nigdy nie lekceważ wartości podejmowania decyzji na podstawie tego, co wiesz. Czasami rozwiązanie nieoptymalne może być bardziej optymalne niż prawidłowe rozwiązanie, ponieważ Ty lub Twój zespół wiesz, jak je stworzyć i utrzymywać lepiej niż rozwiązanie optymalne.

Inną opcją w bazie danych są widoki. W zależności od serwera bazy danych mogą one być wysoce optymalne lub nieoptymalne, a nawet w ogóle nieskuteczne, jedną z wad mogą być czasy zapytań w zależności od opcji indeksowania dostępnych w bazie danych. Widoki stają się jeszcze lepszym wyborem, jeśli nigdy nie musisz dokonywać żadnych modyfikacji danych (wstawianie / aktualizowanie / usuwanie).

Przechodząc obok bazy danych masz stary tryb gotowości do korzystania ze wzorca repozytorium. To sprawdzone podejście może być bardzo skuteczne. Wady zwykle obejmują płytę kotłową, ale dobrze przemyślane repozytoria mogą tego uniknąć, a nawet gdy skutkują niefortunną ilością płyty kotłowej, repozytorium jest zwykle prostym kodem, który jest łatwy do zrozumienia i utrzymania, a także prezentuje dobre API /abstrakcja. Również repozytoria mogą mieć dobrą testowalność jednostkową, którą tracisz z opcjami w bazie danych.

Istnieją narzędzia, takie jak auto-mapper , które mogą sprawić, że użycie ORM będzie możliwe, gdy będą mogły dokonać translacji między modelem bazy danych z orm na użyteczne modele, ale niektóre z tych narzędzi mogą być trudne do utrzymania / zrozumienia bardziej przypominającego magię; chociaż tworzą minimalny narzut kodu, co skutkuje mniejszym nakładem prac konserwacyjnych, gdy jest dobrze zrozumiany.

Następnie przechodzisz coraz dalej od bazy danych , co oznacza, że ​​pojawi się większa ilość kodu, który poradzi sobie z nieprzetłumaczonym modelem trwałości, który będzie naprawdę nieprzyjemny. W tych scenariuszach mówisz o umieszczeniu warstwy tłumaczenia w interfejsie użytkownika, co brzmi jak teraz. Jest to ogólnie bardzo zły pomysł i z czasem strasznie rozpada się.


Teraz zacznijmy mówić szalone .

Nie Objectjest to jedyna abstrakcja, która istnieje na końcu. Przez wiele lat rozwinęła się głębia abstrakcji, że informatyka była badana, a nawet wcześniej z matematyki. Jeśli chcemy zacząć być kreatywni, zacznijmy mówić o znanych dostępnych abstrakcjach, które zostały zbadane.

Jest model aktora.Jest to interesujące podejście, ponieważ mówi, że wszystko, co robisz, to wysyłanie wiadomości do innego kodu, który skutecznie deleguje całą pracę do tego innego kodu, co jest bardzo skuteczne w obudowaniu złożoności z dala od całego kodu. Może to działać, o ile wyślesz do aktora wiadomość z informacją: „Potrzebuję Obj X wysłanego do Y”, a ty masz pojemnik oczekujący na odpowiedź w lokalizacji Y, która następnie przetwarza Obj X. Możesz nawet wysłać wiadomość z instrukcją „Potrzebuję Obj X i obliczeń Y, Z gotowe”, a potem nawet nie trzeba czekać; tłumaczenie występuje po drugiej stronie tego komunikatu i możesz przejść dalej, jeśli nie potrzebujesz odczytać jego wyniku. Może to być niewielkie nadużycie modelu aktora do twoich celów, ale wszystko zależy;

Kolejną granicą enkapsulacji są granice procesu. Można je wykorzystać do bardzo skutecznego segregowania złożoności. Możesz utworzyć kod tłumaczenia jako usługę internetową, w której komunikacja jest prosta HTTP, używając SOAP, REST lub jeśli naprawdę chcesz mieć własny protokół (nie sugerowany). STOMP nie jest wcale złym nowszym protokołem. Lub skorzystaj z normalnej usługi demona z publicznie publikowanym potokiem pamięci w systemie, aby ponownie bardzo szybko się komunikować, używając dowolnego wybranego protokołu. Ma to naprawdę całkiem dobre zalety:

  • Możesz mieć uruchomionych wiele procesów, które tłumaczą jednocześnie starszą i nowszą wersję, umożliwiając aktualizację usługi tłumaczeniowej w celu opublikowania modelu obiektowego V2, a następnie osobno w późniejszym momencie zaktualizuj konsumujący kod do pracy z nowym obiektem Model.
  • Możesz robić ciekawe rzeczy, takie jak przypinanie procesu do rdzenia w celu zwiększenia wydajności, zyskujesz również bezpieczeństwo w tym podejściu, czyniąc ten jedyny proces działający z uprawnieniami bezpieczeństwa, aby dotknąć tych danych.
  • Otrzymasz bardzo silną granicę, gdy mówisz o granicach procesu, które pozostaną stałe, zapewniając minimalny wyciek twojej abstrakcji przez długi czas, ponieważ pisanie kodu w przestrzeni tłumaczenia nie będzie mogło zostać wywołane poza przestrzenią tłumaczenia, ponieważ one nie będzie współużytkować zakresu procesu, zapewniając stały zestaw scenariuszy użycia według umowy.
  • Możliwość asynchronicznych / nieblokujących aktualizacji jest prostsza.

Wady to oczywiście więcej czynności konserwacyjnych niż jest to zwykle konieczne, narzut komunikacji wpływa na wydajność i konserwację.


Istnieje wiele różnych sposobów na zawarcie złożoności, które mogą pozwolić na umieszczenie tej złożoności w coraz bardziej dziwnych i ciekawych miejscach w twoim systemie. Używając form funkcji wyższego rzędu (często sfałszowanych przy użyciu wzorca strategii lub różnych innych dziwnych form wzorców obiektów), możesz zrobić kilka bardzo interesujących rzeczy.

Zgadza się, zacznijmy mówić o monadzie. Możesz utworzyć tę warstwę tłumaczenia w bardzo niezależny sposób, stosując małe, specyficzne funkcje, które wykonują niezbędne tłumaczenia niezależne, ale ukrywając wszystkie te funkcje tłumaczenia, są niewidoczne, więc są one trudno dostępne dla kodu zewnętrznego. Ma to tę zaletę, że zmniejsza na nich poleganie, umożliwiając łatwą zmianę bez wpływu na kod zewnętrzny. Następnie tworzysz klasę, która akceptuje funkcje wyższego rzędu (funkcje anonimowe, funkcje lambda, obiekty strategii, jednak musisz je nadać strukturę), które działają na dowolnym ładnym obiekcie typu OO. Następnie pozwalasz kodowi źródłowemu, który akceptuje te funkcje, wykonać dosłowne wykonanie przy użyciu odpowiednich metod tłumaczenia.

Tworzy to granicę, w której całe tłumaczenie istnieje nie tylko po drugiej stronie granicy , z dala od całego kodu; jest używany tylko po tej stronie, pozwalając reszcie kodu nawet nie wiedzieć o nim nic poza miejscem, w którym znajduje się punkt wejścia dla tej granicy.

Ok, tak, to naprawdę mówi szalone, ale kto wie; możesz być po prostu taki szalony (poważnie, nie podejmuj monad z oceną szaleństwa poniżej 88%, istnieje realne ryzyko obrażeń ciała).

Jimmy Hoffa
źródło
4
Wow, co za niezwykle wyczerpująca odpowiedź. Głosowałbym za tym więcej niż raz, gdyby tylko SE na to pozwoliło.
Marjan Venema
11
Kiedy wychodzi wersja filmowa?
yannis
3
@JimmyHoffa Bravo sir !!! Dodam tę odpowiedź do zakładek i pokażę mojej córce, kiedy się starzeje.
Tombatron
4

Moja sugestia:

Utwórz widoki bazy danych, które:

  1. Nadaj sensowne nazwy kolumnom
  2. Wykonaj „skrzyżowanie z innymi tabelami o różnych warunkach”, aby ukryć tę złożoność.
  3. Konwertuj liczby lub daty przechowywane jako ciągi znaków odpowiednio na liczby i daty.
  4. Stwórz wyjątkowość tam, gdzie jej nie ma, według niektórych kryteriów.

Chodzi o stworzenie fasady, która będzie emulować lepszy projekt na złym.

Następnie spraw, aby ORM odnosił się do tej fasady zamiast prawdziwych stołów.

Nie upraszcza to jednak wstawiania.

Tulains Córdova
źródło
Korzystanie z widoków bazy danych wygląda na świetny pomysł i najbardziej elegancki przebieg działań, które usuwają brzydotę na najniższym poziomie, z jakiegoś powodu nie rozważałem tego. Dziękuję Ci.
DPM
3

Widzę, jak istniejący schemat bazy danych powoduje, że piszesz bardziej szczegółowy kod i zapytania dotyczące zadań, które w innym przypadku mogłyby zostać wyodrębnione za pomocą lepiej zaprojektowanego schematu, ale nie powinno to utrudniać Twojej zdolności do pisania dobrego kodu obiektowego.

  • Pamiętaj o SOLIDNYCH zasadach .
  • Napisz kod, który można łatwo przetestować w jednostce (co często odbywa się zgodnie z zasadami SOLID).
  • Trzymaj logikę biznesową oddzielnie od logiki wyświetlania.
  • Przeczytaj dokumentację i przykłady Apache Wicket - ten framework może prawdopodobnie zaoszczędzić więcej kodu, niż myślisz, więc naucz się go efektywnie używać.
  • Zachowaj logikę, która ma do czynienia z bazą danych, w osobnej warstwie, która zapewnia czysty interfejs, z którym logika biznesowa może współpracować. W ten sposób, jeśli Ty (lub przyszły opiekun) kiedykolwiek dostaniesz szansę na ulepszenie schematu, może to zrobić bez zbyt wielu zmian w logice biznesowej.

Gdy zauważysz, że pracujesz ze schematem bazy danych, który nie jest idealny, łatwo jest uchwycić się wszystkimi sposobami utrudniającymi pracę, ale w pewnym momencie musisz odłożyć na bok te skargi i jak najlepiej je wykorzystać.

Pomyśl o tym jako o możliwości wykorzystania swojej kreatywności do napisania czystego, wielokrotnego użytku, łatwego w utrzymaniu kodu, pomimo niedoskonałego schematu.

Mike Partridge
źródło
1

Odpowiadając na twoje pierwsze pytanie dotyczące lepszego kodu zorientowanego obiektowo, sugerowałbym używanie obiektów mówiących w języku SQL . ORM jest wewnętrznie sprzeczny z zasadami obiektowymi, ponieważ działa na obiekt, a obiekt w OOP jest samowystarczalną jednostką, która posiada wszystkie zasoby do osiągnięcia swojego celu. Jestem pewien, że takie podejście może uprościć kod.

Mówiąc o przestrzeni problemowej, tj. Twojej domenie, postaram się zidentyfikować zagregowane korzenie . Są to granice spójności Twojej domeny. Granice, które absolutnie muszą zachować swoją spójność przez cały czas. Agregaty komunikują się za pośrednictwem zdarzeń domeny. Jeśli masz wystarczająco duży system, prawdopodobnie powinieneś zacząć dzielić go na podsystemy (nazwij go SOA, Microservice, systemy niezależne itp.)

Zastanowiłbym się również nad użyciem CQRS - może to znacznie uprościć stronę zapisu i odczytu. Koniecznie przeczytaj Udi Dahan jest artykuł na ten temat.

Zapadło
źródło