Jedna część mojego programu pobiera dane z wielu tabel i kolumn w mojej bazie danych w celu przetworzenia. Niektóre kolumny mogą być null
, ale w bieżącym kontekście przetwarzania jest to błąd.
To „teoretycznie” nie powinno się zdarzyć, więc jeśli tak, wskazuje to na złe dane lub błąd w kodzie. Błędy mają różne poziomy ważności, w zależności od tego, które pole jest null
; tj. w przypadku niektórych pól przetwarzanie powinno zostać zatrzymane, a ktoś powiadomiony, w innych przypadkach przetwarzanie powinno być kontynuowane i po prostu powiadomić kogoś.
Czy istnieją jakieś dobre zasady architektury lub projektowania, które pozwolą obsłużyć rzadkie, ale możliwe null
wpisy?
Rozwiązania powinny być możliwe do wdrożenia w Javie, ale nie użyłem tagu, ponieważ uważam, że problem jest w pewnym stopniu zależny od języka.
Kilka myśli, które miałem:
Używanie NOT NULL
Najłatwiej byłoby użyć ograniczenia NOT NULL w bazie danych.
Ale co jeśli oryginalne wstawienie danych jest ważniejsze niż ten późniejszy etap przetwarzania? Więc jeśli wstawka wstawi null
do tabeli (albo z powodu błędów, albo może z jakiegoś ważnego powodu), nie chciałbym, żeby wstawka uległa awarii. Powiedzmy, że wiele innych części programu zależy od wstawionych danych, ale nie od tej konkretnej kolumny. Wolę więc zaryzykować błąd w bieżącym kroku przetwarzania zamiast kroku wstawiania. Dlatego nie chcę używać ograniczenia NOT NULL.
Naiwnie w zależności od NullPointerException
Mógłbym po prostu użyć danych tak, jakbym oczekiwał, że będą one zawsze tam istnieć (i tak powinno być naprawdę), i złapać powstałe NPE na odpowiednim poziomie (np. Tak, aby przetwarzanie bieżącego wpisu zostało zatrzymane, ale nie cały postęp przetwarzania ). Jest to zasada „szybko zawieść” i często ją preferuję. Jeśli to błąd, to dostaję zalogowany NPE.
Ale potem tracę zdolność rozróżniania różnych rodzajów brakujących danych. Np. W przypadku niektórych brakujących danych mógłbym je pominąć, ale w przypadku innych przetwarzanie powinno zostać zatrzymane, a administrator powiadomiony.
Sprawdzanie null
przed każdym dostępem i zgłaszanie niestandardowych wyjątków
Niestandardowe wyjątki pozwoliłyby mi zdecydować o właściwej akcji na podstawie wyjątku, więc wydaje się, że to właściwy sposób.
Ale co jeśli zapomnę gdzieś to sprawdzić? Następnie zaśmiecam mój kod zerowymi kontrolami, których nigdy lub rzadko się spodziewamy (a więc zdecydowanie nie są częścią logiki biznesowej).
Jeśli wybiorę tę drogę, jakie wzorce najlepiej pasują do tego podejścia?
Wszelkie uwagi i komentarze dotyczące moich podejść są mile widziane. Także lepsze rozwiązania dowolnego rodzaju (wzorce, zasady, lepsza architektura mojego kodu lub modeli itp.).
Edytować:
Jest jeszcze jedno ograniczenie, polegające na tym, że używam ORM do mapowania DB do obiektu trwałości, więc sprawdzanie wartości null na tym poziomie nie działałoby (ponieważ te same obiekty są używane w częściach, w których null nie wyrządza żadnej szkody) . Dodałem to, ponieważ w dotychczasowych odpowiedziach zarówno wspomniano tę opcję.
Odpowiedzi:
Umieściłbym kontrole zerowe w kodzie mapowania, w którym budujesz swój obiekt z zestawu wyników. To umieszcza sprawdzanie w jednym miejscu i nie pozwoli kodowi przejść do połowy przetwarzania rekordu przed popełnieniem błędu. W zależności od tego, jak działa przepływ aplikacji, możesz chcieć wykonać mapowanie wszystkich wyników jako etap wstępnego przetwarzania zamiast mapowania i przetwarzania każdego rekordu pojedynczo.
Jeśli używasz ORM, będziesz musiał wykonać wszystkie kontrole zerowe przed przetworzeniem każdego rekordu. Polecam do
recordIsValid(recordData)
metody -type, w ten sposób można (ponownie) zachować wszystkie wartości null kontroli oraz inne logiki sprawdzania w jednym miejscu. Na pewno nie przeplułbym kontroli zerowej z resztą logiki przetwarzania.źródło
Wygląda na to, że wstawienie wartości null jest błędem, ale boisz się wymusić ten błąd podczas wstawiania, ponieważ nie chcesz stracić danych. Jeśli jednak pole nie powinno być puste, ale jest, tracisz dane . Dlatego najlepszym rozwiązaniem jest upewnienie się, że pola zerowe nie zostaną błędnie zapisane.
W tym celu należy wymusić poprawność danych w jednym wiarygodnym, stałym repozytorium tych danych, bazie danych. Zrób to, dodając ograniczenia zerowe. Wówczas kod może się nie powieść, ale awarie te natychmiast powiadamiają o błędach, co pozwala naprawić problemy, które już powodują utratę danych. Teraz, gdy możesz łatwo zidentyfikować błędy, przetestuj kod i przetestuj go dwa razy. Będziesz w stanie poprawić błędy prowadzące do utraty danych, a proces znacznie uprościć przetwarzanie danych, ponieważ nie musisz się martwić o wartości zerowe.
źródło
W odniesieniu do tego zdania w pytaniu:
Zawsze doceniałem ten cytat (dzięki uprzejmości tego artykułu ):
Zasadniczo: brzmi to tak, jakbyś popierał Prawo Postela , „bądź konserwatywny w tym, co wysyłasz, bądź liberalny w tym, co akceptujesz”. Chociaż świetna w teorii, w praktyce ta „zasada niezawodności” prowadzi do oprogramowania, które nie jest niezawodne , przynajmniej w dłuższej perspektywie - a czasem także w krótkim okresie. (Porównaj artykuł Erica Allmana The Resustness Principle Reconspled , który jest bardzo dokładnym podejściem do tematu, aczkolwiek głównie skupiony na przypadkach użycia protokołu sieciowego).
Jeśli masz programy, które nieprawidłowo wstawiają dane do bazy danych, programy te są zepsute i muszą zostać naprawione . Zgłaszanie problemu pozwala tylko gorzej się pogłębiać; jest to odpowiednik inżynierii oprogramowania umożliwiający uzależnionemu kontynuowanie uzależnienia.
Pragmatycznie rzecz biorąc, czasami trzeba włączyć „zepsute” zachowanie, aby przynajmniej tymczasowo kontynuować, szczególnie w ramach płynnego przejścia od stanu rozluźnionego, zepsutego do stanu ścisłego, poprawnego. W takim przypadku chcesz znaleźć sposób, aby umożliwić nieprawidłowe wstawianie, ale nadal pozwól, aby „kanoniczny” magazyn danych zawsze był w poprawnym stanie . Można to zrobić na różne sposoby:
Jednym ze sposobów uniknięcia tych wszystkich problemów jest wstawienie kontrolowanej warstwy API między programami, które wydają zapisy, a rzeczywistą bazą danych.
Wygląda na to, że częścią problemu jest to, że nie znasz nawet wszystkich miejsc, które generują nieprawidłowe zapisy - lub że jest ich po prostu zbyt wiele, abyś mógł je zaktualizować. Jest to przerażający stan, ale nigdy nie powinno się pozwolić, by powstało.
Gdy tylko pojawi się więcej niż kilka systemów, które mogą modyfikować dane w kanonicznym magazynie danych produkcyjnych, będziesz miał kłopoty: nie ma sposobu, aby centralnie utrzymywać cokolwiek na temat tej bazy danych. Lepiej byłoby pozwolić na jak najmniejszą liczbę procesów do wydawania zapisów i używać ich jako „strażników”, którzy mogą przetwarzać dane przed wstawieniem, jeśli to konieczne. Dokładny mechanizm tego zależy od konkretnej architektury.
źródło
„ Czy istnieją jakieś dobre zasady architektury lub projektowania do obsługi rzadkich, ale możliwych zerowych pozycji? ”
Prosta odpowiedź - tak.
ETL
Wykonaj wstępne przetwarzanie, aby upewnić się, że dane mają wystarczającą jakość, aby przejść do bazy danych. Wszystko w pliku upuszczenia należy zgłosić z powrotem, a wszelkie czyste dane można załadować do bazy danych.
Jako ktoś, kto był zarówno kłusownikiem (deweloperem), jak i opiekunem gry (DBA), wiem z gorzkiego doświadczenia, że strony trzecie po prostu nie rozwiążą problemów z danymi, chyba że będą do tego zmuszone. Ciągłe pochylanie się do tyłu i masowanie danych przez zestaw stanowi niebezpieczny precedens.
Mart / Repository
W tym scenariuszu nieprzetworzone dane są wypychane do bazy danych repozytorium, a następnie zdezynfekowana wersja jest wypychana do bazy danych mart, do której aplikacje mają dostęp.
Wartości domyślne
Jeśli możesz zastosować rozsądne wartości domyślne do kolumn, powinieneś, choć może to wymagać trochę pracy, jeśli jest to istniejąca baza danych.
Niepowodzenie wcześnie
Kuszące jest po prostu rozwiązywanie problemów z danymi w bramie do aplikacji, pakietu raportów, interfejsu itp. Zdecydowanie odradzam poleganie wyłącznie na tym. Jeśli podłączysz jakiś inny widget do bazy danych, potencjalnie znów będziesz mieć do czynienia z tymi samymi problemami. Rozwiąż problemy z jakością danych.
źródło
Ilekroć twój przypadek użycia pozwala bezpiecznie zastąpić NULL dobrą wartością domyślną, możesz wykonać konwersję w instrukcjach
SELECT
Sql za pomocąISNULL
lubCOALESCE
. Więc zamiastmożna pisać
Oczywiście będzie to działać tylko wtedy, gdy ORM pozwala bezpośrednio manipulować instrukcjami select lub zapewniać zmienne szablony do generowania. Należy upewnić się, że żadne „rzeczywiste” błędy nie są maskowane w ten sposób, więc zastosuj je tylko wtedy, gdy zastąpienie domyślną wartością jest dokładnie tym, czego chcesz w przypadku wartości NULL.
Jeśli jesteś w stanie zmienić bazę danych i schemat, a twój system db obsługuje to, możesz rozważyć dodanie klauzuli wartości domyślnej do konkretnych kolumn, zgodnie z sugestią @RobbieDee. Będzie to jednak wymagać modyfikacji istniejących danych w bazie danych w celu usunięcia wcześniej wstawionych wartości NULL, a następnie usunie możliwość rozróżnienia prawidłowych i niekompletnych danych importu.
Z własnego doświadczenia wiem, że używanie ISNULL może zadziwiająco dobrze - w przeszłości musiałem zachować starszą aplikację, w której pierwotni twórcy zapomnieli dodać ograniczenia NOT NULL do wielu kolumn i nie mogliśmy łatwo dodać tych ograniczeń później z pewnych powodów. Ale w 99% wszystkich przypadków 0 jako domyślny dla kolumn liczbowych i pusty ciąg jako domyślny dla kolumn tekstowych był w pełni akceptowalny.
źródło
OP zakłada odpowiedź, która łączy reguły biznesowe ze szczegółami technicznymi bazy danych.
To są wszystkie reguły biznesowe. Reguły biznesowe nie dbają o sam w sobie zero. Mimo wszystko wie, że baza danych może mieć wartość null, 9999, „BOO!” ... To tylko kolejna wartość. To, że w RDBMS null ma interesujące właściwości, a unikalne zastosowania są dyskusyjne.
Liczy się tylko to, co oznacza „nieważność” dla danych obiektów biznesowych ...
Tak.
Zgłaszanie wyjątku przy pobieraniu danych nie ma sensu.
Pytanie brzmi: „czy powinienem przechowywać„ złe ”dane? To zależy:
źródło
Istnieje wiele sposobów obsługi wartości zerowych, więc przejdziemy od warstwy bazy danych do warstwy aplikacji.
Warstwa bazy danych
Możesz zabronić zerowania ; chociaż tutaj jest to niepraktyczne.
Możesz skonfigurować domyślne dla poszczególnych kolumn:
insert
, więc nie obejmuje wyraźne wprowadzenie zerowejinsert
błędnie pominięto tę kolumnęMożesz skonfigurować wyzwalacz , aby po wstawieniu brakujące wartości były automatycznie obliczane:
insert
Warstwa zapytania
Możesz pominąć wiersze, w których występuje niedogodność
null
:Możesz podać wartość domyślną w zapytaniu:
Uwaga: instrumentowanie każdego zapytania niekoniecznie stanowi problem, jeśli masz jakiś automatyczny sposób ich generowania.
Warstwa aplikacji
Możesz wstępnie sprawdzić tabelę pod kątem zabronionych
null
:Możesz przerwać przetwarzanie, gdy napotkasz zabronione
null
:null
a które nieMożesz pominąć wiersz, gdy napotkasz zabronione
null
:null
a które nieMożesz wysłać powiadomienie, gdy napotkasz zakaz
null
, pojedynczo lub partią, co jest uzupełnieniem innych sposobów przedstawionych powyżej. Najważniejsze jest jednak „co wtedy?”, W szczególności, jeśli oczekujesz, że wiersz zostanie załatany i potrzebujesz ponownego przetworzenia, być może będziesz musiał upewnić się, że masz jakiś sposób na odróżnienie już przetworzonych wierszy od wierszy wymagających jest ponownie przetwarzany.Biorąc pod uwagę twoją sytuację, poradziłbym sobie z sytuacją w aplikacji i połączyłem albo:
Chciałbym po prostu pominąć, jeśli to możliwe, aby w jakiś sposób zagwarantować odrobinę postępu, szczególnie jeśli przetwarzanie może zająć trochę czasu.
Jeśli nie trzeba ponownie przetwarzać pominiętych wierszy, wystarczy je zarejestrować, a wiadomość e-mail wysłana na końcu procesu z liczbą pominiętych wierszy będzie trafnym powiadomieniem.
W przeciwnym razie użyłbym tabeli bocznej, aby wiersze zostały naprawione (i ponownie przetworzone). Ten boczny stolik może być albo prostym odniesieniem (bez klucza obcego), albo pełnoprawną kopią: ta ostatnia, nawet jeśli jest droższa, jest konieczna, jeśli nie masz czasu, aby się zająć,
null
zanim będziesz musiał wyczyścić główne dane.źródło
Wartości zerowe mogą być obsługiwane w tłumaczeniu lub mapowaniu typów baz danych na typy językowe. Na przykład w języku C #, oto ogólna metoda, która obsługuje dla ciebie dowolny typ:
Lub, jeśli chcesz wykonać akcję ...
A następnie w odwzorowaniu, w tym przypadku na obiekt typu „Próbka”, obsłużymy null dla dowolnej kolumny:
Na koniec wszystkie klasy odwzorowań mogą być generowane automatycznie na podstawie zapytania SQL lub powiązanych tabel, analizując typy danych SQL i tłumacząc je na typy danych specyficzne dla języka. To właśnie wiele ORM robi dla Ciebie automatycznie. Należy pamiętać, że niektóre typy baz danych mogą nie mieć bezpośredniego mapowania (geo-przestrzenne kolumny itp.) I mogą wymagać specjalnej obsługi.
źródło