Dlaczego zrobiliśmy osobny rejestr RCP, mimo że mieliśmy ProcessingActivity
Mieliśmy ProcessingActivity z Sprint D i RcpEntry z głównej Iodynki. Dwa modele, te same dane, zero synchronizacji. Po roku zdecydowaliśmy.
No to. Sprint D, dawno temu. Robiliśmy DPIA-flow i okazało się że potrzebujemy modelu dla pojedynczej czynności przetwarzania. Powstał ProcessingActivity. Wtedy.
Pół roku później ; Iodynka, RCP wg Art. 30. Trzeba było rejestru. Ktoś (ja) napisał osobny model RcpEntry. “Bo ProcessingActivity ma inne pola.” Ma. Ale nie aż tak inne.
TL;DR
- Dwa modele dla tej samej rzeczy = pewny rozjazd danych w ciągu roku
- Próba stopniowej fuzji przez "shared interface" nic nie daje
- Lepiej zerwać plaster: wybrać jeden model, zmigrować, drugi skasować
- Pełna fuzja zajęła nam 3 dni; codzienna utrata sanity ; 6 miesięcy
Problem ; jak to rozjeżdżało się w produkcji
Kategorie danych były duplikowane. Czasem właściwie powiązane, czasem nie. DPIA jeden raz wzięła sobie listę z ProcessingActivity, a klient czytał ją w RcpEntry. Mówiłem “ale tu jest, tylko trzeba kliknąć drugą zakładkę”. Klient: “to po co dwie zakładki”.
Pół biedy gdyby tylko widoki. Mieliśmy w obu miejscach pole legalBasis. W jednym była enum, w drugim string. Eksport DPIA do PDF generowany z drugiego. Klient zmienia w pierwszym ; PDF nie zmienia się. Sygnał DORA-007 (DPIA outdated) nie odpalał, bo patrzył na drugi.
Decyzja ; usuwamy ProcessingActivity, zostaje RcpEntry
Wybór był arbitralny ; oba modele dało się równie dobrze rozszerzyć. Wybrałem RcpEntry bo nazwa jest jednoznaczna (Rejestr Czynności Przetwarzania), a ProcessingActivity to zachodni żargon który nikt w polskim biurze IODa nie używa.
Rozwiązanie ; jak to zrobić bez zatrzymania produkcji
Trzy etapy: najpierw przenosimy resztę pól z ProcessingActivity do RcpEntry. Potem zmieniamy wszystkie miejsca w kodzie żeby pisały do RcpEntry. Potem migracja danych ; backfill RcpEntry z istniejących ProcessingActivity. Potem dropujemy ProcessingActivity.
Krok 1 ; rozszerzamy schemat
Pola które były tylko w ProcessingActivity (np. retentionPeriodDays, profilingFlag) lądują w RcpEntry. Migracja prismy generuje nullable kolumny. Nie zmieniamy nic w API jeszcze.
Krok 2 ; podwójny zapis (transitional)
Endpoint który tworzył ProcessingActivity teraz tworzy też RcpEntry. Ten sam ID (UUID), te same dane. Przez tydzień obserwujemy czy się synchronizuje. Synchronizuje.
Krok 3 ; backfill historyczny
Skrypt prisma który chodzi po wszystkich ProcessingActivity i tworzy odpowiadające RcpEntry. 4212 rekordów, 9 sekund. Sanity check: count(distinct id) match w obu tabelach.
Krok 4 ; przepinamy reads
Wszystkie miejsca które czytały z ProcessingActivity teraz czytają z RcpEntry. Search and replace + ESLint rule blokujący prisma.processingActivity.* (już to robimy dla DoraSupplier).
Krok 5 ; drop
Po dwóch tygodniach pewności ; DROP TABLE processing_activity. Backup baza zachowana 30 dni.
Co odpadło
Pomysł utrzymania dwóch modeli i synchronizacji przez database trigger. Działa, ale każda zmiana schematu wymaga update triggera i to się odzywa po roku. Lepiej wybrać i zrezygnować.
Drugie ; pomysł żeby ProcessingActivity zostawić jako “view” nad RcpEntry. Można, ale generuje dwa places to look at i nikt nie wie który jest źródłem prawdy.
Pisał ktoś z zespołu RealSec. Jak chcesz pogadać o tym konkretnie ; napisz do nas.