ProduktyModułyDokumentacjaKalkulatoryCennikBlogO nas EN Zaloguj Zobacz demo

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.

Pułapka: "Dwa modele można pogodzić shared interfejsem TypeScript." Można. Ale prisma nie patrzy na interfejsy ; tylko na schemę. I jak ktoś dorzuca pole tylko w jednym, drugi zostaje. Trzy miesiące i już są rozjechane.

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.

Decyzja: jeden model, RcpEntry. ProcessingActivity dropped przez @@map() na RcpEntry, potem dropowany w pełni po 2 sprintach z legacy resolverem dla starych ID-ów.

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.

Wygrana: jeden rejestr. Klient widzi to samo co system. DPIA prefill działa. Eksport DOCX ma spójne dane. Sygnały lecą zgodnie z prawdą.

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.

← Wszystkie notatki