Kiedyś łączyłem się modemem 7,2 kb/s

…i nauczyłem się, że w aplikacjach klient-serwer, WSZYSTKO co można przerzucić na serwer bazy danych, to powinno się tam implementować. Wyliczenie wartości faktury na podstawie kilkudziesięciu jej składników, rabatów po przekroczeniu kwoty faktury, rabatów dodatkowych w związku z grupą zakupową, etc, etc, etc… to wszystko obliczało się w bazie danych, by otrzymać tylko końcowy wynik w postaci kwoty do zapłacenia.

Korzyści były oczywiste:

  1. bezpieczeństwo transakcji - wszystkie procedury składowane wykonywane w ramach jednej transakcji.
  2. brak przesyłania ogromnej ilości danych z serwera na stację klienta i później iterowanie po każdym wierszu i przesyłanie tego znowu na serwer.

Wywoływało się WyliczFakture(id_faktury) i czekało chwilkę na odpowiedź.
Nie ukrywam, że jestem zwolennikiem takiego rozwiązania, gdzie ok 80% całej logiki aplikacji było w procedurach składowanych i triggerach bazy danych.

A jak to się ma do Railsów?
Co mam zrobić przenosząc aplikację klient-serwer do środowiska Rails?
Czy chcąc zbudować nową aplikację mam zrezygnować z tego, co jest doskonale wytestowane i działało przez ponad 10 lat, czy też może konstruować tak apkę railsową, by i tutaj wywoływać procedury składowane w bazie danych z odpowiednimi parametrami?
Czy w ogóle ma sens takie konstruowanie aplikacji railsowych?

Edit:
Zaznaczam, że problem dotyczy przenoszenia starej aplikacji klient-serwer.

W latach 2000-2005 pracowalem nad systemem obliczajacym stopy zwrotu funduszy inwestycyjnych, tworzacym rankingi funduszy, grup funduszy itp itd. Calosc byla pisana od samego poczatku przy zalozeniu, ze wszystkie obliczenia beda wykonywac się jako procedury składowane, odpalane automatycznie przy każdej zmianie danych. Działalo to na PostgreSQL, ktory juz wtedy mial niezle wsparcie do pisania triggerow i procedur skladowanych (zwlaszcza, gdy pojawił się PL/pgSQL, na poczatku trzeba bylo je pisac w C :wink:

Zalety były oczywiste:

  • obliczenia odpalaja się nawet w przypadku prostych / recznych modifikacji,
  • przez to działają ZAWSZE, bez wzgledu na to jakiego ‘klienta’ bazy uzywasz,
  • wszystko działa z definicji w obrebie transakcji i na niskim poziomie, zatem obliczenia sa szybkie przy zagwarantowanej spojnosci danych

Z czasem jednak zaczął wychodzic fundamentalny problem - debuggowanie. Dla prostych obliczen nie wystepuje, ale jeśli:

  • wstawienie rekordu odpala triggera, ktory
  • oblicza/aktualizuje proste dane ‘pochodne’, po czym widzi, ze wstawiony rekord jest ostatnim brakujacym z danej grupy, czyli
  • moze odpalic kolejne procedury, ktore moga zaczac obliczac srednie z danej grupy,
  • ktore wstawiaja rekordy, odpalajace kolejne triggery itp. itd.

robi sie z tego niezle drzewo, a w ekstremalnym przypadku mozna czekac na wynik calosci (ktora pierwotnie wyglada jako proste wstawienie rekordu) pare minut albo i dluzej. Albo nigdy, jesli w kodzie jest blad, i taka propagacja np. nigdy sie nie skonczy.

Wiec pytanie nr 1 - czy łatwiej taka logike pisac, w PL/pgSQL, czy w innym jezyku np. Ruby.

Developerzy PostgreSQL szybko dali mozliwosc pisania funkcji w innych jezykach - ktore byly osadzane w bazie. Wiec mozna bylo korzystac z PL/Java, PL/perl :wink: czy tez PL/ruby

Ale problem pozostawal - trudnosc w ‘sledzeniu’ takich kaskadowych obliczen oraz ich natura ‘wszystko albo nic’ sprawia, ze gdybym dzis podejmowal znow decyzje odnosnie architektury, to raczej robilbym to jako obliczenia wykonywane w ramach transakcji, ale wykonywane ‘na zewnatrz’ bazy, a nie wewnatrz.

Moja odpowiedz jest taka - jesli obliczenia sa stosunkowo proste, to moze miec to sens (trzymania ich w bazie). Ale jest sa skomplikowane i trudne do ‘sledzenia’ (co jest kwestia posiadania odpowiednich narzedzi - ale nawet, jesli istnieja ‘zaawansowane narzedzia’, to moze nie warto komplikowac prostych rzeczy), to wyciagniecie ich ‘poza’ baze ma wiekszy sens niz trzymanie ich w srodku.

A jeszcze inną kwestią jest ‘ostatnio’ modne (powrot po 20 latach) podejscie ‘thick client’, gdzie serwer wystawia jedynie proste API, zaś cała logika jest wykonywana po stronie klienta (w obecnym swiecie w przeglądarce / telefonie).

Dzięki za te informacje.
Zgadzam się ze wszystkim co napisałeś, ale niestety nie przybliżyło mnie to do odpowiedzi na pytanie, co mam zrobić z istniejącym aktualnie (i działającym produkcyjnie) systemem. :frowning:

System nie będzie raczej rozwijany.
Może jakiś niewielki fragment.
Obecnie całość działa bardzo dobrze właśnie dzięki tym drzewkom o których wspominałeś (procedura aktualizuje, uruchamia trigger, on oblicza …, itd).

Jakie pomysły macie, by to przenieśc do Railsów?

  1. Budowa od nowa całości
  2. Budowa interfejsu w railsach i wywoływanie sprawdzonych w boju procedur

Co z tym teraz zrobić?
(Na marginesie dodam, że nawet wstawianie klienta i tym podobnych danych jest realizowane przez wywoływanie procedury składowanej z odpowiednimi parametrami)

No dobra, a po co to przenosić “do Railsów” skoro sam mówisz , że praktycznie już nie będzie to rozwijane, a wszystko działa jak trzeba?

Jestem w bardzo podobnej sytuacji: poprzednia aplikacja klient - serwer z logiką rozproszoną pomiędzy klienta i serwer bazy danych (triggery, procedury). Obecne rozwiązanie? Przepisuję/tworzę na nowo aplikację w RoR. Na obecnym etapie nie miałem potrzeby, by w ogóle korzystać z procedur, bo aplikacja i baza danych pracują na jednej maszynie (w przyszłości, jeśli zajdzie taka potrzeba, w jednym klastrze). Oczywiście wykorzystuję transakcje, ale tworzone z poziomu aplikacji. Pisze się znacznie przyjemniej i nie ma problemów z debugowaniem (od prostego inspecta, po faktyczne użycie debuggera). Triggery jak na razie zastępuję callbackami ActiveRecord, choć nie zarzekam się, że nie będę musiał z nich skorzystać, kiedy przyjdzie czas na implementację bardziej złożonych funkcjonalności. Baza, to PostgreSQL.

Powody są dwa:

  1. rosnące wymagania klientów
  2. …pieniądze.

Aplikacja działa pod każdą Windą (Windows 95+) i była od samego początku napisana w architekturze klient-serwer.
Ponieważ były to jeszcze czasy, gdy ludzie nie mieli w firmach nawet sieci LAN :smiley: , więc serwer i klient na jednej maszynie stali. :smiley:
Gdy sieci LAN trafiły pod strzechy i ludzie uświadomili sobie, że mogę równocześnie pracować na kilku stanowiskach, to “klientów” dorzucono do kilku stacji i świat stał się piękny.
Świat jednak poszedł do przodu i niektórym :wink: zamarzyło się, by mieć dostęp do tych danych z domu, w delegacji, u klienta, etc…
Ponieważ dostęp do świata jest w tych firemkach realizowany niejednokrotnie dzięki modemom GSM + router, więc pomysł, by tak zestawiać połączenia do bazy jest, delikatnie mówić, słaby. Problemy ze zrywaniem połączeń, czyli sesji connect bazy, brakiem ochrony danych… No dużo tego.
Zrodził się zatem pomysł, by bazy trafiły znowu na jedną maszynę!

Został postawiony Windows Terminal Server i ludzie mają na swoich komputerach tylko link do odpowiednio skonfigurowanych zdalnych połączeń.
Aplikacja działa jako RDP (Remote Deskop Protocol) i z punktu widzenia użytkownika działa DOKŁADNIE tak jak kiedyś (lokalnie). Pojawiają się jednak problemy. Sesje terminalowe potrafią się zawieszać (np. błąd Windows’a 7, gdy “zrzucisz” program na pasek) i opłaty związane z licencjami dla serwera terminali są bardzo wysokie.
Ponadto Winda jak to winda… Wymaga silniejszych maszyn, z większymi dyskami, bo na 1 procku i 500MB RAM, to nawet systemu nie zainstalujemy, więc opłaty hostingowe też są wyższe.
Mówiąc krótko, czas pożegnać Billa Gats’a. :smile:
Ponadto ludzie chcą mieć dodatki wbudowywane typu jakieś kalendarze, wysyłane maile automatycznie, itd…
Nie będzie to już jakiś szalony rozwój całej aplikacji, a raczej dodatków wokół niej

Rozbudowywanie tamtej aplikacji, to droga w kierunku czarnej d…ziury.
Baza, która de facto wykonuje ok 90% “czarnej roboty” jest nadal świetna, ale dostęp do niej należy moim zdaniem zreorganizować.

No to pozostaje chyba tylko wycenić ile czasu / pieniędzy zajmie napisanie tego od nowa - czego akurat zazdroszcze(chyba kazdy z nas po kilku latach rozwijania duzej apki , chcialby napisac ja od nowa znając już dobrze domenę problemu, ale raczej klientowi ciezko przetlumaczyc ze “kod bylby ladniejszy , a programista bedzie spac spokojniej więdzać , że architektura cacko ;p”) i się dowiedzieć czy te wymagania o których piszesz są na tyle istotne i pilne , że klient jest w stanie pokryć koszt przepisanie tego. Każda inna drogą tj. np. : pisanie części w railsach i próby integracji z tym co już masz to imho ciężka i ryzykowna droga.

Ja bym to przeniósł ‘po kawałku’, a więc raczej 2-etapowo:

  • zachował istniejace procedury i opakował je w kontrolery / modele Railsowe,
  • a potem i tylko tam, gdzie ma to sens (być może wszędzie), przepisałbym procedury, wynosząc je do klas Rubyego

Ma to taki plus, że w trakcie/po 1szym etapie, procedury nadal powinny działać, bo nie bedziesz ich dotykać. A w trybie ‘piszemy wszystko od nowa’, nic nie będzie działać przez kolejne N miesięcy :wink:

Od ActiveRecord-owych callbacków trzymałbym się z daleka. Pomijając to, jak pięknie komplikują unit testy, z tego co kojarzę Railsy przechowują je w postaci stringa (sic!), który jest wywoływany przez eval - co jest głownym powodem, dla którego Aaron Patterson radzi by ‘use the f***** “save”’ :wink: Wszystkie before/after save mozna albo jawnie zdefiniowac w re-implementacji “save”, albo opakowac w osobnej klasie, która bedzie zarządzac tym, co dzieje sie podczas tworzenia/aktualizacji obiektu ‘bazowego’.

Chciałbym zwrócić uwagę na jedną kwestię. Użycie procedur składowanych nie oznacza automatycznie użycia triggerów. Można napisać logikę biznesową w procedurach na bazie, ale ograniczyć stosowanie triggerów do minimum. Pracuję od wielu lat przy (dosyć starych) systemach opartych na bazie danych Oracle. W wielu z nich mamy dużo kodu w PL/SQL, jednak bazodanowcy z którymi pracuję zgodnie powtarzają, żeby nie przesadzać z triggerami, bo prowadzi to do niekontrolowanych zachowań systemu. Natomiast można napisać większość kodu w procedurach na bazie, które będą wywoływane z klienta lub schedulera. Taki kod nie wygląda ładnie (programowanie strukturalne), ale radzi sobie wydajnościowo z dużą ilością przetwarzanych danych i jest w miarę kontrolowalny.

Dokładnie o czymś takim myślałem.
Zastanawiam się tylko, kiedy wdepnę na jakąś minę.
Gdzie tutaj są zagrożenia itd, bo na chłopski rozum, to dzisiaj ten klient (napisany w C++) służy też w większości tylko do prezentacji danych i przekazywania parametrów do procedur w bazie, zatem Railsy też (chyba) tak będą potrafiły.

Problem polega na tym, że w tej bazie takie triggery już są i to wszystko świetnie ze sobą aktualnie współdziała. Moim “planem” jest jak najmniejsze ingerowanie w tę bazę, a co za tym idzie, nie będę też usuwał żadnych triggerów, by przenosić ich logikę na poziom kontrolerów i modeli railsowych.
Na pewno nie na tym etapie I, o którym wspomniał Mark

Moim zdaniem to wdepniesz na mine już w momencie kiedy podjemiesz decyzje, że będziesz logike z bazy przepisywał na Railsy i zaczniesz to robić. Jeśli logika jest rozproszona po trigerach a jest ich dużo to będzie problem ze zrozumieniem jak to działa i dlaczego pewne rzeczy są liczone tak a nie inaczej.
Ja od wielu lat pracuje przy bazach danych (pisze procedury skladowane) i potwierdzam to co napisal @bartek1 i nie potwierdzam tego że trudno sie debuguje logike zaszyta w bazie danych. Tak samo trudno jak aplikacje w innym jezyku - jak sie narobi balaganu to i trudno to sie potem debuguje :slight_smile:
Z drugiej strony uzywanie Railsow z procedurami moze byc trudne. Kiedys tak bylo - model byl przywiazany do ActiveRecord i napisanie aplikacji ktora nie opierala sie na ORM, tylko na innch sposobach dostepu do bazy, bylo mozliwe ale trzeba bylo sie troche upisac :wink: Trzeba bylo samemu napisac warstwe modelu, ktora bedzie “gadala” z pozostalymi elementami Railsow. Ja zatrzymalem sie na wersji 3.0 Railsow wiec nie wiem jak to jest teraz - moze teraz jest latwiej.

Jesli nie ma innych przeciwskazan,i tak jak napisales baza dziala super i nie bedzie zmieniana, to ja bym napisal i wystawil api do bazy jako webservis. W jakimkolwiek jezyku przy wykorzystaniu mikroframeworka lub nawet w jednoplikowym skrypcie napisac api, ktore ma przyjac zadanie i zwrocic dane w dogodnym dla Ciebie formacie. Nie potrzebujesz do tego MVC ani ORMa, wystarczy prosty skrypt, ktory bedzie wywolywal procedury w bazie. I juz polowe aplikacji masz gotowe, wygrzane i przetestowane :slight_smile:
Reszte mozesz napisac w Railsach i z nich wolac utworzone api wtedy kiedy trzeba. Odseparujesz sie od starej bazy i nie bedziesz musial sie meczyc w railsowych modelach z wywolywaniem procedur skladowanych bazy albo przepisywac logiki bazy na railsy, ktora to logike bedziesz musial potem dobrze przetestowac.