Czy dopuszczanie pól zdefiniowanych przez użytkownika jest złą praktyką?

17

Zasadniczo, czy uważa się za złą praktykę zezwalanie na pola tworzone przez użytkowników w bazie danych dla aplikacji WWW?

Na przykład robię dla mojej żony aplikację internetową do inwentaryzacji domu i będzie chciała zdefiniować własne pola dla różnych przedmiotów. Planowałem pozwolić jej na tworzenie kategorii przedmiotów i dodawanie „funkcji” do tych kategorii. Funkcje byłyby po prostu kluczem / wartością przechowywaną jako ciągi znaków. W ten sposób, gdyby miała kategorię o nazwie „Audio CD”, mogłaby dodawać funkcje do rzeczy takich jak „artysta”, „utwory” itp. Ale w innej kategorii, jak „meble”, mogłaby dodawać funkcje do rzeczy takich jak „materiał” „(drewno, plastik itp.). Wtedy dowolny element może należeć do jednej (lub wielu) kategorii, dodając te funkcje do elementu.

Widzę problemy, w których wyszukiwanie za pomocą tych funkcji wymaga porównań ciągów, nie ma sprawdzania poprawności danych itp. Zgodnie ze zwinną metodologią może lepiej byłoby, gdyby wymyśliła nowe kategorie i atrybuty, a ja po prostu musiałbym stworzyć nowe tabele Jak idziemy. W moim przykładzie jest to mała baza użytkowników (2 z nas), a liczba utworzonych rekordów byłaby niewielka, więc nie jest tak źle.

Ogólnie rzecz biorąc, jak ludzie radzą sobie z czymś takim w „prawdziwym życiu”?

zako42
źródło
4
Czy rozważałeś użycie bazy danych zorientowanej na dokumenty, takiej jak MongoDB? Możesz przechowywać dokument według typu, który działa jak schemat, który można również edytować (prawdopodobnie ręcznie, biorąc pod uwagę małą skalę projektu).
Andy Hunt
@AndyBursh jednym z „zabawnych” bitów z obecnymi postgresami jest typ danych „json” ( link ). Takie podejście pozwoliłoby przechować pola określone przez użytkownika w tych danych, er, dokumentować, cokolwiek, a następnie wykorzystać resztę pól do rzeczy, na których właściwe indeksy i tym podobne. Chociaż wszystko zależy od sposobu użytkowania i trudno powiedzieć, czy zadziałałoby to dobrze dla określonej aplikacji, czy nie. Ale należy o tym pamiętać.
wszystko: świetna dyskusja, dziękuję za cały wgląd! @AndyBursh Słyszałem o MongoDB, ale tak naprawdę nigdy o nim nie czytałem. Brzmi jak kolejny domowy projekt do eksperymentowania z ...
zako42

Odpowiedzi:

19

Kiedy zaczynasz docierać do „pól zdefiniowanych przez użytkownika”, co często znajduje się w modułach do śledzenia błędów, zarządzanie zasobami klientów i podobne narzędzia biznesowe polegają na tym, że nie są one wspierane tabelą zawierającą pola bajillionowe (jeśli tak, to prawdopodobnie jest to problem jego).

Zamiast tego znajdziesz projekty tabeli wartości atrybutów encji i powiązane narzędzie administracyjne do zarządzania prawidłowymi atrybutami.

Rozważ następującą tabelę:

  + -------------- +
  | rzecz |
  | -------------- |
  | id |
  | typ |
  | desc |
  | attr1 |
  | attr2 |
  | attr3 |
  | attr4 |
  | attr5 |
  + -------------- +

Dzieje się tak po dodaniu kilku atrybutów. Zamiast attr1udawać, że czyta artistlub trackslub genrelub cokolwiek atrybuty rzecz ma. A zamiast 5, co gdyby było 50. Oczywiście nie da się tego zarządzać. Wymaga również aktualizacji modelu i ponownego wdrożenia aplikacji w celu obsługi nowego pola. Nieidealny.

Teraz rozważ następującą strukturę tabeli:

  + -------------- + + --------------- + + ------------- +
  | rzecz | | thing_attr | | attr |
  | -------------- | | --------------- | | ------------- |
  | id | <--- + | thing_id (fk) | +> | id |
  | typ | | attr_id (fk) | + - + | nazwa |
  | desc | | wartość | | |
  + -------------- + + --------------- + + ------------- +

Masz swoje rzeczy z podstawowymi polami. Masz jeszcze dwa tabele. Jeden z atrybutami. Każde pole jest wierszem w attrtabeli. I jest thing_attrjeszcze para kluczy obcych odnoszących się do thingstołu i attrstołu. I to ma pole wartości, w którym przechowuje się dowolną wartość pola dla tego bytu.

A teraz masz strukturę, w której tabela attr może być aktualizowana w czasie wykonywania, a nowe pola można dodawać (lub usuwać) w locie bez znaczącego wpływu na ogólną aplikację.

Zapytania są nieco bardziej złożone, a sprawdzanie poprawności również staje się bardziej złożone (albo funky procedury składowane, albo cała strona klienta). To kompromis w projektowaniu.

Rozważ także sytuację, w której pewnego dnia musisz przeprowadzić migrację i wrócisz do aplikacji, aby stwierdzić, że istnieje teraz około pół tuzina więcej atrybutów niż schemat, który pierwotnie rozpowszechniłeś. To sprawia, że ​​brzydkie migracje i aktualizacje, w przypadku których tabela wartości atrybutu jednostki, gdy jest używana poprawnie, może być czystsza. (Nie zawsze, ale może być.)


Czy są jakieś wady modyfikowania schematu w czasie wykonywania? Jeśli użytkownik uważa, że ​​rzecz wymaga nowego atrybutu, wystarczy dynamicznie dodać kolumnę do tabeli?

Jeśli pracujesz z odpowiednim smakiem bazy danych nosql, prawdopodobnie mógłbyś to zrobić (zauważ, że odpowiedni smak nosql do tego prawdopodobnie byłby magazynem klucz-wartość, który jest, no cóż, tabelą EAV dla relacyjnych opisanych powyżej) bez większych problemów. Jednak zawiera wszystkie kompromisy dotyczące nosql, które zostały szczegółowo opisane w innym miejscu.

Jeśli zamiast tego pracujesz nad relacyjną bazą danych - musisz mieć schemat. Dynamiczne dodanie kolumny oznacza, że ​​niektóre podzbiory następujących rzeczy są prawdziwe:

  • Robisz programowanie dla baz danych. Zamiast czystego mapowania tej kolumny do tego pola za pomocą ładnej ORM, prawdopodobnie robisz takie rzeczy, select *a następnie robisz skomplikowany kod, aby dowiedzieć się, co to właściwie są dane (zobacz wynik w Javie ResultSetMetaData ), a następnie przechowywanie tego w mapie ( lub inny typ danych - ale niezbyt ładne pola w kodzie). To z kolei rzuca sporo bezpieczeństwa tekstowego i literowego, które masz dzięki tradycyjnemu podejściu.
  • Prawdopodobnie porzuciłeś ORM. Oznacza to, że piszesz surowy kod SQL dla całego kodu, zamiast pozwolić systemowi wykonać pracę za Ciebie.
  • Zrezygnowałeś z robienia czystych aktualizacji. Co się stanie, gdy klient doda pole z jedną nazwą, którego używa także Twoja następna wersja? W witrynie matchmakingu uaktualnienie, które chce dodać hasdatepole do przechowywania znacznika czasu, zostało już zdefiniowane jako hasdateboolean dla udanego dopasowania ... a aktualizacja zostanie zerwana.
  • Ufasz, że klient nie zepsuje systemu, używając jakiegoś słowa zastrzeżonego, które również łamie twoje zapytania… gdzieś.
  • Związałeś się z jedną marką bazy danych. DDL z różnych baz danych jest inna. Typy baz danych są najprostszym tego przykładem. varchar2vs texti tym podobne. Twój kod, aby dodać kolumnę, działałby na MySQL, ale nie w Postgres, Oracle ani SQL Server.
  • Czy ci zaufać klientowi rzeczywiście dodać dane dobrze ? Jasne, EAV jest daleki od ideału, ale teraz masz jakieś przerażające, niejasne nazwy tabel, których twórca nie dodał, z niewłaściwym typem indeksu (jeśli istnieje), bez żadnych ograniczeń w kodzie, w których trzeba być i tak dalej.
  • Użytkownik uruchomił aplikację, nadając uprawnienia do modyfikacji schematu. Małe tabele upuszczania Bobby'ego nie są możliwe, gdy jesteś ograniczony do SQL zamiast DDL (na pewno możesz to zrobić delete * from students, ale naprawdę nie możesz zepsuć bazy danych w zły sposób). Liczba rzeczy, które mogą pójść nie tak z dostępem do schematu po wypadku lub złośliwej aktywności, gwałtownie rośnie.

To naprawdę sprowadza się do „nie rób tego”. Jeśli naprawdę tego chcesz, skorzystaj ze znanego wzorca struktury tabeli EAV lub bazy danych całkowicie poświęconej tej strukturze. Nie pozwól ludziom tworzyć dowolnych pól w tabeli. Bóle głowy po prostu nie są tego warte.

DougM
źródło
4
Wymyśliłeś także bazę danych na nowo.
user253751
1
@immibis dodał warstwę, w której użytkownik może administrować bez zmieniania reszty bazy danych lub konieczności ponownego wdrożenia w celu aktualizacji modelu.
1
@immibis EAV od lat gorąco debatuje w kręgach relacyjnych baz danych. Teoretycznie nie jest to konieczne, ale w praktyce nie można bez tego robić pewnych rzeczy.
Ross Patterson
1
@ShivanDragon, który stosuje podejście NoSQL. Magazyn dokumentów po prostu przechowuje dokumenty i nie narzuca schematu. W związku z tym dodawanie i usuwanie pól oraz parsowanie dokumentów jest całkowicie poza zakresem samej bazy danych (i napisałeś swój model, aby to uwzględnić). Jest to zupełnie inny zestaw kompromisów niż kompromis relacyjnej bazy danych dla struktury EAV.
1
Powiązana dyskusja na
5

Robienie tego dobrze jest trudne.

W przypadku jednorazowej aplikacji, takiej jak to, co planujesz, możesz oczywiście po prostu dodać kolumnę dla każdego pola i podać interfejs użytkownika, który sprawia, że ​​definiowanie pola przez nieprzeszkolonych użytkowników jest bezpieczniejsze niż podawanie wiersza poleceń SQL. Możesz też zastosować się do przerażającego wzorca Entity-Attribute-Value , który jest klasyczną, choć nieco przerażającą odpowiedzią na tego rodzaju problem. Budowanie interfejsu użytkownika w celu zdefiniowania pól EAV jest zwykle znacznie bardziej skomplikowane niż w przypadku kolumn bazy danych, a zapytania mogą stać się dość włochate, ale w przypadku dużej liczby pól ( tj. Schematów o bardzo rzadkiej macierzy) może to być jedyny sposób na uzyskanie praca wykonana.

Ross Patterson
źródło
Podsumowując: mały projekt == KISS. Zwinny do ziemi.
Encaitar
Problem z aktualizacjami tabel bazy danych polega na tym, że w zależności od ilości danych i wymaganych indeksów (pola niestandardowe często wymagają funkcji wyszukiwania) kwerenda zmieniająca tabelę może zająć ogromną ilość czasu. Krótko mówiąc, MySQL i inne relacyjne bazy danych po prostu nie są dobrym medium dla tego rodzaju wymagań.
Oddman,
0

Ostatnio spotkałem coś podobnego.

Zrobiłem 2 stoły.

1: table Objects 
    Id , name, type

On jest wszystkimi twoimi przedmiotami. U ustawiłeś jego nazwę.

I typ tego obiektu: - dla mnie dostępne typy to ekwipunek, ekwipunek_wyprzedaż, biuro.

I zwykła konfiguracja była n przedmiotów lub ekwipunku, który jest również dzieckiem biura i użyłem tabeli łączenia, aby połączyć obiekty ze sobą

2 table settings 
     organization_Id , title, value , type

Tabela ustawień zawiera każdą nazwę pola dla tego konkretnego typu obiektu i wartość.

Przykładowe właściwości biura

Lokalizacja, telefon, godziny pracy

I dla przedmiotów

  • Ilość
  • Cena £
  • kod kreskowy

Itd, wszystkie te właściwości są wymuszane przez Twój model i zapisywane w tabeli ustawień jako osobne wiersze (ale używaj zamiany zamiast wstawiania, aby uniknąć wielu wierszy dla tego samego pola)

Kiedy więc chcę mieć biuro, ładuję je łatwo ze wszystkimi jego relacjami i ustawieniami, w których znajdują się ustawienia object_I'd (żądane obiekty)

Następnie przestawiam wszystkie wiersze z ustawień i to wszystko.

W przypadku, gdy chciałem, aby ustawienie było specyficzne dla elementu w ekwipunku (nie globalnym), ustawiam object_I'd = Chciałbym z tabeli relacji object_objects i ustawiam settings.type = zestaw_konfiguracji

Mam nadzieję, że rozumiesz, co mam na myśli, że spróbuję sformatować odpowiedź, gdy dojdę do laptopa

Zalaboza
źródło
2
Wskazówka profesjonalna - nie publikuj na tym forum z telefonu. Automatyczna korekta powoduje, że części Twojego postu są nieczytelne.
BobDalgleish,
Haha fajna obserwacja :)
Zalaboza
0

Czy dopuszczanie pól zdefiniowanych przez użytkownika jest złą praktyką?

Nie, to nie jest zła praktyka. To jest dość powszechne. W kategoriach OO nazywa się to dziedziczeniem. Masz inwentarz klasy podstawowej i dwie odziedziczone klasy AudioCD i meble.

Ogólnie rzecz biorąc, jak ludzie radzą sobie z czymś takim w „prawdziwym życiu”?

Musisz zdecydować, w jaki sposób zapasyItem, AudioCD i meble są przechowywane w bazie danych.

Jeśli najważniejsze jest dla Ciebie łatwe zapytanie, a db-space / normalizacja nie ma znaczenia, zaimplementuj schemat „Tabela według hierarchii”.

Jeśli najważniejsza jest dla Ciebie przestrzeń / normalizacja, a bardziej skomplikowane zapytania nie stanowią problemu, zaimplementuj schemat „Tabela według typu”.

Aby uzyskać więcej informacji, zobacz dziedziczenie dotnet table-per-typ-vs-table-hierarchy-dziedziczenie lub hibernacja java .

k3b
źródło
Nie wiem, czy to rozwiązuje pytanie. Użytkownik nie modyfikuje kodu, aby utworzyć nowe klasy
Colin D