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.