Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

17 stycznia 2014

Temporal data – czyli w jakim departamencie Pani pracowała w maju 2008?

Dziś chciałbym spojrzeć na dane w nieco innym świetle niż robimy to na co dzień. Nie ważne czy tworzymy duży rozproszony system czy prostą aplikację klient-serwer zawsze pojawia się kontekst danych.  Podstawowym repozytorium danych we współczesnych aplikacjach, szczególnie biznesowych, są relacyjne bazy danych. Jak zwykle postrzegamy te dane? Jak o nich myślimy?

W klasycznym podejściu nie rozpatrujemy danych w kontekście czasu. Załóżmy, że mamy tabelę przechowującą pracowników. Wiemy, że dane pracownika X mogą się zmieniać na przestrzeni tygodni, miesięcy, lat…Co w sytuacji gdy chcielibyśmy nagle cofnąć się w czasie do konkretnego punktu z przeszłości i obejrzeć naszego pracownika? Hmm … no nic! W klasycznym systemie nie możemy czegoś takiego zrobić, ponieważ dane nie posiadają pojęcia czasu. Inaczej mówiąc system przechowuje bieżące dane, które są takie same w każdym punkcie czasu.

Co zatem z tym zrobić? Tutaj pojawia się pojęcie: temporal database. To baza danych w której dane posiadają kontekst czasu. Mówię tutaj o bazach danych, ale pojęcie „czasowości” danych należy postrzegać znacznie szerzej. Na wyższym poziomie abstrakcji także możemy wprowadzić czasowość, tj. np. w obiektach naszego modelu domeny.

Aspekt „czasowości” danych możemy rozpatrywać w co najmniej na dwa sposoby, a zależy to od wymagań naszego systemu. Mianowicie nasze dane mogą posiadać atrybut ValidTime, który wprowadza kontekst czasu w sensie obowiązywania danych w świecie rzeczywistym. Inaczej mówiąc określa on w jakim czasie konkretna wersja naszych danych jest prawdziwa z punktu widzenia świata rzeczywistego.  Drugi sposób to wprowadzenie atrybutu TransactionTime, który również określa dane w czasie, ale interpretacja jest tu nieco inna. To podejście ma zastosowanie gdy interesuje nas raczej czas rejestracji danych w systemie, ale nie moment zaistnienia jakiegoś faktu w rzeczywistym świecie. Aby to nieco rozjaśnić, to musimy sobie uzmysłowić, że fakt zarejestrowania danych w systemie mógł nastąpić później niż faktyczna zmiana tych danych w realnym świecie. Stąd podział na takie dwa przypadki.

Istnieje jeszcze trzeci sposób będący kombinacją dwóch poprzednich nazywany bitemporal.

Wprowadźmy oś czasu i rozpatrzmy przykład prostego obiektu Pracownik posiadającego dwa pola: ImieNazwisko, Departament.

2004-08-01: Id = 1, ImieNazwisko = „Janina Nowak”, Departament = „PR”

2005-11-15: Id = 1, ImieNazwisko = „Janina Nowak”, Departament = „HR”

2009-08-08: Id = 1, ImieNazwisko = „Janina Kowalska”, Departament  = „HR”

2013-01-01: Id = 1, ImieNazwisko = „Janina Kowalska”, Departament  = „IT”

Teraz naszego pracownika możemy rozpatrywać w kontekście czasu. Załóżmy, że powyższe daty odnoszą się do świata rzeczywistego, a nie momentu zarejestrowania tych faktów w systemie. Wiemy że nasz byt rozpoczął pracę w 2004 roku w departamencie PR i wiemy w jakim departamencie pracował w maju 2008. Dodatkowo wiemy, że nasz pracownik, a raczej pracowniczka w międzyczasie zmieniła nazwisko.

Wprowadziliśmy dodatkowy atrybut ValidTime określający czas od jakiego obowiązuje konkretna wersja danych tego samego pracownika.

Zastanówmy się zatem jak przechowywać tego typu dane w relacyjnej bazie danych. Sposobów jest kilka, ale niewątpliwie korzystne wydaje się być wprowadzenie okresu czasu. Dzięki okresowi czasu tj. atrybutom ValidTimeFrom i ValidTimeTo możemy wydajniej i prościej wyszukiwać dane w naszym systemie. Ciężar wtedy przenosi się na zapis danych. I tak np. nasz pracownik będzie wyglądał teraz tak:

ValidTimeFrom = 2004-08-01, ValidTimeTo =   2005-11-14, Id = 1, ImieNazwisko = „Janina Nowak”, Departament = „PR”

ValidTimeFrom = 2005-11-15, ValidTimeTo =  2009-08-07, Id = 1, ImieNazwisko = „Janina Nowak”, Departament = „HR”

ValidTimeFrom = 2009-08-08, ValidTimeTo =  2012-12-31, Id = 1, ImieNazwisko = „Janina Kowalska”, Departament = „HR”

ValidTimeFrom = 2013-01-01, ValidTimeTo =  NULL, Id = 1, ImieNazwisko = „Janina Kowalska”, Departament = „IT”

Kluczem identyfikującym nasz obiekt (jego konkretną wersję w czasie) jest jego Id i ValidTimeFrom. Pole ValidTimeTo jest tylko polem pomocniczym ułatwiającym dostęp do danych.

Łatwo możemy pobrać dane obowiązujące w konkretnym punkcie czasu:

SELECT ImieNazwisko, Departament FROM Pracownicy WHERE Id = 1 AND ‚2008-05-01’ BETWEEN ValidTimeFrom AND ValidTimeTo

Jak widać zostało tu poczynione założenie, że wszystkie dane z całego zakresu czasu znajdują się w jednej tabeli. Innym rozwiązaniem jest przechowywanie danych bieżących w jednej tabeli, a danych archiwalnych w drugiej np. Pracownicy_Archiwum. Ten drugi wariant jednakże ma sens w sytuacji używania TransactionTime w naszym systemie. Mianowicie, gdy nasz system ma obowiązek rejestrowania historii zmian poczynionych na danych, ale sięga do nich sporadycznie – wybierzmy wariant nr 2.

A co z gotowymi rozwiązaniami?

Co prawda pojęcie temporal tables pojawiło się w standardzie SQL:2011, ale obecnie jeszcze niewiele baz danych obsługuje tą funkcjonalność. Jeśli używacie MS SQL Server – zapomnijcie! Jedną z ciekawszych implementacji, moim zdaniem, proponuje IBM w DB2 od wersji 10.

A co z modelem domeny?

Świat zareagował oczywiście na potrzeby odwzorowania danych temporal także w modelu obiektowym. Powstało kilka wzorców projektowych odnoszących się mniej lub bardziej do tego zagadnienia. Sam Martin Fowler zaangażował się w ten temat i omawia kilka z nich tutaj.

Słowem podsumowania dodam, że póki co programiści związani z technologiami Microsoft (.NET, SQL Server) muszą pokusić się o własne implementacje.

Moim celem nie było dostarczenie konkretnego i uniwersalnego rozwiązania, bo takie pewnie nie istnieje, a raczej nakreślenie problemu i zachęcenie do zgłębienia wiedzy na ten temat.

Stwórz darmową stronę albo bloga na WordPress.com.