Relacyjne bazy danych i iteracyjny rozwój

19

W wielu podejściach do tworzenia oprogramowania, takich jak metodyki zwinne, projektowanie oparte na domenie oraz analiza i projektowanie zorientowane obiektowo, zachęcamy do stosowania jednego iteracyjnego podejścia do programowania.

Dlatego nie powinniśmy robić naszego modelu domeny poprawnie przy pierwszym uruchomieniu projektu. W miarę upływu czasu zmieniamy model, ponieważ z czasem lepiej rozumiemy dziedzinę problemową.

Poza tym, nawet jeśli staramy się uzyskać idealny model z góry, co, jak już jestem przekonany, jest bardzo trudne, wymagania mogą się zmienić. Więc po program został wdrożony do produkcji, użytkownicy końcowi mogą zauważyć, że pewien wymóg nie został do końca poznany, lub, co gorsza, niektóre wymóg brakowało.

Chodzi o to, że po wdrożeniu oprogramowania może się okazać, że będziemy musieli zmienić model. Jeśli tak się stanie, mamy problem: produkcyjna baza danych zawiera ważne dane użytkownika i jest już dopasowana do formatu starego modelu .

Aktualizacja kodu może być trudnym zadaniem, jeśli kod nie jest dobrze zaprojektowany i jeśli system jest duży. Ale można to zrobić z czasem, mamy narzędzia takie jak Git, które pomagają nam to zrobić bez uszkodzenia wersji gotowej do produkcji.

Z drugiej strony, jeśli model się zmieni, jeśli właściwości klas znikną, czy cokolwiek innego, baza danych również powinna się zmienić. Ale mamy problem: istnieją już dane, których nie można utracić, co jest już sformatowane dla starego modelu.

Wydaje się, że relacyjna baza danych stanowi tutaj barierę uniemożliwiającą iteracyjny rozwój, a nawet aktualizację oprogramowania, gdy jest to wymagane przez użytkowników końcowych.

Jednym ze sposobów, które już zastosowałem, było kodowanie specjalnej klasy, która mapuje stare tabele bazy danych na nowe. Zatem klasy te pobierają dane w starym formacie, konwertują je na format używany przez nowy model i zapisują w nowych tabelach.

To podejście nie wydaje się najlepsze. Moje pytanie brzmi: czy istnieją dobrze znane i zalecane podejścia do pogodzenia iteracyjnego rozwoju z relacyjnymi bazami danych?

użytkownik1620696
źródło
6
Nawiasem mówiąc, nie sądzę, aby miało to związek w szczególności z relacyjnymi bazami danych. Mam podobny problem z projektem, nad którym pracuję, ale mamy go ze schematem dla naszych ciągów JSON, które reprezentują bardzo nierelacyjne obiekty. Prawdopodobnie wpływa jednakowo na wszystkie formy uporczywości.
Ixrec
1
Zmieniasz schemat bazy danych w sposób, który nie powoduje utraty danych, en.wikipedia.org/wiki/Schema_migration .
RemcoGerlich
1
Jestem pewien, że ten temat był już gdzieś szeroko omawiany, po prostu nie mogę go znaleźć u Programistów. Ale zobacz tutaj martinfowler.com/articles/evodb.html lub tutaj stackoverflow.com/questions/334059/…
Doc Brown
1
„Poza tym, nawet jeśli staramy się uzyskać z góry idealny model, co, jak już jestem przekonany, jest bardzo trudne, wymagania mogą się zmienić”. Chciałbym dodać, że nie powinieneś nawet próbować uzyskać modelu (zbliżonego do idealnego) z przodu. Może to ograniczyć twój sposób myślenia do jednego rodzaju rozwiązań zamiast pozostawiać otwarte opcje.
Wygięty

Odpowiedzi:

15

Nie muszą to być specjalne klasy, ale tak, potrzebujesz czegoś, co zabierze bazę danych w poprzednim formacie i skonwertuje ją na bieżącą.

Chodzi o to, że musisz opracować proces pisania i testowania tych skryptów i dyscypliny, aby nigdy nie dotykać ręcznie testowych i produkcyjnych baz danych, ale zawsze za pomocą skryptów migracyjnych.

Za każdym razem, gdy musisz dokonać zmiany w bazie danych, piszesz skrypt, który to zrobi, zarówno w SQL, jak i przy użyciu warstwy ORM, i zatwierdzasz ją do kontroli wersji wraz ze zmianami, które wymagają nowego schematu. Następnie masz skrypt sterujący, który zaktualizuje bazę danych, stosując wszystkie skrypty migracji, które nie zostały jeszcze zastosowane w sekwencji.

I upewnij się, że modyfikujesz tylko dowolne udostępnione środowiska programowania, testowania i kontroli jakości, stosując skrypty i wycofując je do wcześniejszej wersji, jeśli nie działają, więc możesz mieć pewność, że będą działać zgodnie z przeznaczeniem, gdy uruchomisz je w środowisku produkcyjnym .

Nowa instalacja odbywa się po prostu przez zastosowanie wszystkich skryptów. Po pewnym czasie możesz mieć ich setki i myśleć, że jest to bardzo nieefektywne, ale nie wpadnij w pułapkę próby optymalizacji. Instalacja jest jednorazową czynnością, a jej niezawodność jest atutem, dzięki czemu jest szybka.

@Doc Brown już połączył Martin Fowler: Ewolucyjny projekt bazy danych i /programming/334059/agile-development-and-database-changes , i dodałbym Alexa Papadimoulisa: Zmiany w bazie danych zrobione dobrze , co jest krótsze i ma kilka przykładów.

Jako porządny przykład narzędzia implementującego taki proces sugeruję Alembic . Opiera się na frameworku Python SQLAlchemy , ale można go używać z innymi językami i strukturami, jeśli nie mają one własnej obsługi migracji. Strona Wikipedii poświęcona migracji schematów zawiera więcej takich narzędzi .

Jan Hudec
źródło
1
@ Tibo budujesz schemat od zera, uruchamiając tę ​​samą sekwencję skryptów. W ten sposób radzisz sobie z problemem. Biorąc pod uwagę, że standardowo można przejść z dowolnej instancji bazy danych - w tym również takiej, która jeszcze nie istnieje - do bieżącego schematu i mieć pewność, że jest taki sam. Twój przykład nie wymaga dwóch sposobów. (Przynajmniej nie otrzymano spójnej linii bazowej - pierwszym krokiem jest ustalenie linii bazowej, a kiedy dojdziesz do tej linii bazowej, problem zniknie.)
Murph
1
Kciuki w górę za artykuł Alexa; może nie być krótszy, ale sprawia, że ​​czytanie jest dużo bardziej praktyczne i zabawne.
Murphy,
1
Jesteśmy sklepem Agile i prowadzimy 100% usługę bezawaryjną, a obie dotyczą również DB. Migrujemy schemat produkcji średnio raz dziennie, a ja poparłbym wszystko, co powiedział Jan. Jedną dodatkową rzeczą, którą robimy, która była nieoceniona, jest to, co nazywamy testowaniem migracji, które działa w ramach naszego procesu kompilacji i wdrażania. Usuwa migawkę schematu z produkcji, stosuje wszelkie oczekujące migracje z systemu głównego do niego, a następnie uruchamia testy jednostkowe z obecnie wdrożonego kodu produkcyjnego w odniesieniu do tego schematu. Celem jest sprawdzenie, czy zastosowanie migracji nie spowoduje uszkodzenia działającego systemu.
Gordon Wrigley,
1

O dziwo, to jest właśnie ten problem, przed którym stoi mój obecny zespół programistów. Pytanie zawiera kilka pytań cząstkowych, więc zostaną one rozwiązane niezależnie.

Przede wszystkim, czy relacyjna baza danych zbyt mocno ogranicza model danych, utrudniając zmiany?

Z pewnością , ale niekoniecznie z podanych powodów. Niestety wszechstronność systemów zarządzania relacyjnymi bazami danych prowadzi również do ich upadku. RDBMS został pierwotnie opracowany, aby oferować stosunkowo prostą platformę do przechowywania danych, która akceptuje duże zestawy danych i zmniejsza je do stosunkowo niewielkich rozmiarów. Dokonano tego kosztem złożoności modelu danych i wymaganej mocy obliczeniowej. Wraz ze wzrostem złożoności bazy danych powstały procedury składowane, widoki, funkcje i wyzwalacze, aby pomóc administratorom baz danych w radzeniu sobie ze złożonością w spójny i skalowalny sposób.

Niestety model relacyjnej bazy danych nie jest zorientowany obiektowo i naturalnie nie odwzorowuje obiektów rzeczywistych, jak powinien model danych. To prowadzi nas do potrzeby użycia narzędzi pośredników, takich jak mapery obiektowo-relacyjne i tym podobne. Niestety, podczas gdy narzędzia te mają wyraźne miejsce w dzisiejszym świecie programistycznym, ich użycie jest ukierunkowane jedynie na objaw problemu relacyjnej złożoności danych, a nie na przyczynę leżącą u jego podstaw, czyli niedopasowanie modelu danych do świata rzeczywistego.

Prowadzi to do drugiej części pytania, która była raczej założeniem, ale powinno być postrzegane jako pytanie: czy powinniśmy zrobić dobry model domeny za pierwszym razem?

Tak, do pewnego stopnia. Jak wskazano w pytaniu, rzadko jest możliwe pełne zrozumienie problemu, gdy rozpoczynamy proces projektowania. Jednak różnica między całkowicie niepoprawnym modelem danych, w przeciwieństwie do modelu, który może być modyfikowany w miarę lepszego zrozumienia domeny, to model, który spójnie odwzorowuje rzeczywisty świat. Oznacza to, że musimy dołożyć wszelkich starań, aby stworzyć początkowy model danych, który byłby zgodny z naszym rozumieniem problemu w kategoriach jego rzeczywistych podmiotów. Jeśli zaczniemy normalizować niewłaściwe jednostki, model danych będzie zły na dwa sposoby, a odzyskiwanie będzie trudne.

Pod wieloma względami przejście na rozwiązania baz danych „Bez SQL” wynika z problemów związanych z niespójnością modelu danych. Wykorzystanie zorientowanego obiektowo podejścia bez SQL powoduje, że myślimy więcej o mapowaniu między naszymi obiektami w kodzie a tymi w prawdziwym świecie - a kiedy napotykamy niespójność, często jest to oczywiste, ponieważ jest niemożliwe do zaimplementowania w naszym Baza danych. To prowadzi do lepszego ogólnego projektu.

To prowadzi do ostatniego pytania: czy relacyjny model danych jest niespójny z podejściem zwinnym?

Nie, ale wymagana jest większa umiejętność. Podczas gdy w świecie bez SQL dodawanie pola lub konwertowanie właściwości na tablicę jest trywialne, robienie tych rzeczy w świecie relacyjnym wcale nie jest trywialne. Potrzeba co najmniej kogoś, kto jest w stanie zrozumieć zarówno relacyjny model danych, jak i realne podmioty, które reprezentują. Ta osoba jest osobą, która ułatwi aktualizację modelu relacyjnego w miarę rozumienia zmian modelu rzeczywistego. Nie ma srebrnej kuli, aby rozwiązać ten problem.

theMayer
źródło
1
Naprawdę mam nadzieję, że przesadziłeś z problemem utworzenia nowego pola w tabeli RDBMS, aby uczynić to zdanie bardziej dramatycznym. Tabela bazy danych musi być bardzo specjalna (lub nowy typ pola musi być czymś wyjątkowym), aby naprawdę stworzyć problem z dodaniem jednego pola.
Alexey Zimarev
Tak, ale nigdy nie jest to tylko jedno pole ...
May
1
Powiedziałbym częściej, że to tylko jedno pole. Dramatyczne zmiany schematu nie są tak częste. Nie jestem fanem używania RDBMS z projektem OO z powodu niedopasowania impedancji. Jednak dodawanie nowych typów (tabel) i właściwości (kolumn) jest stosunkowo łatwe w obu światach, chociaż w NoSQL jest to naprawdę trochę łatwiejsze. Ale w obu przypadkach złożone zmiany są bólem. Co gorsza, staje się to w systemie opartym na zdarzeniach z migawkami, a wręcz przeciwnie, jak przyjemne jest tworzenie takiego systemu.
Alexey Zimarev
Widzę, że relacyjne bazy danych są często używane jako „uniwersalny młot” do rozwiązywania problemów związanych z przechowywaniem danych - w rzeczywistości istnieją bardzo konkretne powody, aby z nich korzystać. W starannie przemyślanym systemie rzadko trzeba martwić się problemami, o których pisałem w mojej odpowiedzi - zwracam się do bardziej ogólnej grupy odbiorców, która może nie mieć doświadczenia, aby z góry opracować odpowiedni projekt systemu.
theMayer
Nie ma rozbieżności między modelem relacyjnym i zwykle odwzorowuje on rzeczywisty świat tak dobrze, jak każdy inny model. Niektóre operacje będą łatwiejsze dla jednego rodzaju, a inne dla innego rodzaju. Problem polega na tym, że tworzysz model jednego rodzaju (obiektowy) i próbujesz go zaimplementować za pomocą narzędzi innego rodzaju (relacyjnych). To nie działa dobrze. Ale świat rzeczywisty nie jest obiektowy. Po prostu jest i ty go modelujesz. I muszę użyć odpowiednich narzędzi dla wybranego rodzaju modelu.
Jan Hudec
-1

Nie chodzi o to, aby refaktoryzować tak bardzo, że model zmienia się nie do poznania. Nawet przy iteracyjnym rozwoju powinieneś naprawdę budować na istniejących elementach, a nie przekształcać je na części.

Daje to 2 główne opcje radzenia sobie z dużymi zmianami, gdy tylko się pojawią: pierwszą jest zbudowanie warstwy DB jako interfejsu API, użycie procedur przechowywanych, aby można je było dostosować do klienta bez zmiany podstawowego schematu danych.

Innym sposobem jest zastąpienie tabel odrobiną migracji danych. Gdy wymagana jest zmiana na dużą skalę, tworzysz nowy schemat i wdrażasz zestaw skryptów, aby przenieść stare dane i wmasować je w nowy format. Wykonanie tego kosztuje czas, dlatego polegasz bardziej na tańszych metodach modyfikacji dostępu do danych (np. Za pośrednictwem SP) jako pierwszego wyboru.

Więc: 1. staraj się myśleć z wyprzedzeniem, aby nie trzeba było niczego zmieniać.

  1. Polegaj na opakowaniach lub interfejsach API, więc zmiany są ograniczone lub mogą być ukryte w izolowanym komponencie

  2. Jeśli zajdzie taka potrzeba, poświęć trochę czasu na prawidłowe uaktualnienie.

Te kroki dotyczą wszystkiego, nie tylko baz danych.

gbjbaanb
źródło
Podstawowy schemat czasami wymaga zmiany. Gdy aplikacja przechodzi testy klientów, pojawiają się nowe atrybuty, o których nigdy nie słyszałeś, atrybuty, o których myślałeś, że liczby okazują się ciągami, relacje, których spodziewałeś się 1: 1, wcale nie będą takie same. Tego rodzaju rzeczy nie można ukryć za procedurami przechowywanymi (poza tym procedury przechowywane są częścią problemu, ponieważ, podobnie jak inne rzeczy w bazie danych, nie podlegają kontroli wersji).
Jan Hudec
@ JanHudec od kiedy SP nie działają w kontroli wersji? Możesz obejmować takie rzeczy, zmieniasz interfejs API SP, aby pobrać ciąg i zapisać go w innym polu, obsługując stare liczby i nowe ciągi w odrobinie kodu w SP. Nie jest to najładniejsze, ale może być lepsze niż udanie się do każdej witryny klienta w celu migracji ich danych do nowego formatu ciągu (są lepsze przykłady, ale masz pomysł). Jeśli zmiana okaże się duża, musisz przeprowadzić migrację, ale przynajmniej w przypadku interfejsu API DB masz także inne, tańsze opcje.
gbjbaanb
Nadal musisz przejść do każdej witryny klienta, aby zainstalować SP i dodać nowe pole. A kiedy już tam jesteś, możesz także migrować dane. SP są przydatne, ponieważ umożliwiają utworzenie interfejsu kompatybilnego wstecz, jeśli masz dostęp do bazy danych przez wiele aplikacji, więc nie musisz aktualizować wszystkich jednocześnie. Ale nie zapisują żadnych kroków, gdy schemat musi się zmienić z powodu zmieniających się wymagań.
Jan Hudec