Na tej stronie:
Zmiana wersji sprawy
Tak jak zostało to opisane we wstępie, najczęściej zmiana wersji sprawy następuje automatycznie podczas zmiany typu sprawy (definicji opisującej sprawę). Jest to spowodowane tym, że trudno jest jednoznacznie ocenić intencję systemu realizującego zapis istniejącej sprawy, który jednocześnie zmienia jej definicję i zidentyfikować dlaczego typ został zmieniony. Wydaje się, że najlepszym rozwiązaniem jest, że gdy algorytm rozpoznający typ sprawy, zidentyfikuje zmianę jego definicji, nastąpi dodanie nowej sprawy, z nową definicją typu, zachowując spójny obraz danych aktualizowanego obiektu. Zapis wraz ze zmiana wersji sprawy powoduje:
- Poprzedni obraz sprawy zachowany zostaje ze statusem 'Z' (zamknięta), bez zmiany pozostałych wartości parametrów (pól).
- Powstaje nowa sprawa (nowa instancja obiektu) ze statusem 'A' (aktywna) z nową definicją typu i nowymi, zaktualizowanymi wartościami pól.
Bardzo istotnym jest fakt by z łatwością można było znaleźć poprzednią wersję sprawy. Dlatego też w nagłówku sprawy zdefiniowane zostały następujące pola wspierające wyszukiwanie poprzednich wersji:
previousVersionId
- pole zawierające wartość identyfikatora poprzedniej wersji sprawyrootVersionId
- pole zawierające wartość identyfikatora pierwszej wersji sprawy
Zależności pomiędzy poszczególnymi wersjami spraw można opisać w następującej relacji (przykładowe dane wartości identyfikatorów spraw):
numer wersji sprawy | caseId | previousVersionId | rootVersionId |
---|---|---|---|
Pierwsza wersja | 12345 | null | 12345 |
Druga wersja | 12346 | 12345 | 12345 |
Trzecia wersja | 12347 | 12346 | 12345 |
Czwarta wersja | 12348 | 12347 | 12345 |
..... |
Jak widać na podstawie powyższej tabeli wartość rootVersionId
wskazuje zawsze na pierwszą wersję sprawy, a previousVersionId na sprawę w poprzedniej wersji.
Pamiętaj, że tylko jedna z wersji spraw (ostatnia) może być aktywna, czyli posiadać status inny niż 'Z' (zamknięta). W podanym w tabeli przykładzie pole status
będzie przyjmować następujące wartości (zobacz też artykuł Context i CaseHeader z opisem pola status):
numer wersji sprawy | status (dozwolone wartości) |
---|---|
Pierwsza wersja | Z |
Druga wersja | Z |
Trzecia wersja | Z |
Czwarta wersja | A, O, N, Z |
Dzięki takiej organizacji danych zawsze szybko możemy odnaleźć niezbędne dane dotyczące poszczególnych wersji. Na przykład szybko możemy znaleźć wszystkie wersje sprawy za pomocą zapytania Lucene (przykład z wykorzystaniem usługi CaseBusinessAction.searchLuceneByQueryXML):
<query>mrc_rootVersionId:118904</query>
Konsekwencje zmiany wersji sprawy
Zawsze możemy podjąć akademicką dyskusję na temat tego czy zmiana wersji sprawy podczas zmiany definicji typu jest słuszna i czy implementacja, takie a nie inne zachowanie Mercury DB (HgDB) jest prawidłowe. Podczas realizacji rozwiązania zawsze powinniśmy się kierować regułą złotego środka, kompromisu, a jak wiadomo kompromis sprawia że wszyscy są niezadowoleni. Dlatego warto przedstawić wady i zalety takiego a nie innego uniwersalnego rozwiązania:
Wady | Zalety |
---|---|
Ginie bezpośrednie powiązanie pomiędzy sprawą, dla której zmieniana jest wersja, a inną sprawą nadrzędną w definicji sprawy złożonej. Zmiana powoduje, że nowa wersja nie jest już powiązana, bo powiązanie oparte jest o identyfikatory spraw. Sprawa nadrzędna nadal pozostaje powiązana ze starą wersją sprawy. | Utrzymane zostaje powiązanie pomiędzy poprzednią wersją sprawy a inną sprawą nadrzędną, a tym samym zachowana jest spójność definicji całego obiektu sprawy złożonej. Pamiętajmy, że pracujemy z obiektami - zmiana definicji fragmentu obiektu powoduje zmianę definicji całości - przyjęte rozwiązanie zapobiega takim zdarzeniom. |
Nowa definicja typu sprawy może być zupełnie inna niż dotychczasowa - zapobiegamy utracie danych, która wynikałaby z braku możliwości umiejętnej, automatycznej konwersji różnych typów spraw. | |
Brak możliwości aktualizacji poprzednich wersji spraw. | Stare wersje spraw nadal są dostępne w systemie i mogą być wykorzystywane w trybie READ-ONLY. |
Nie trzeba realizować procesów migracji danych gdy zmieni się model danych wykorzystywany przez aplikacje składujące dane w Mercury DB (HgDB) |
Zapobieganie zmianom wersji spraw
Najczęstszym problemem jest NIEŚWIADOMA, niezamierzona zmiana wersji sprawy.
Pamiętaj! Model danych spraw jest dynamiczny i jest weryfikowany za każdym razem gdy wykonywany jest zapis sprawy. Najczęściej spotykanym błędem jest zła interpretacja pola typu String
oraz Text
. Pola tych typów są bardzo podobne i jeżeli nie zostanie to jawnie przedstawione w żądaniu zapisu sprawy może dojść do nadinterpretacji przesyłanych wartości. Przykładem może być wysłanie pola o nazwie name
od długości mniejszej niż 64 znaki. Mercury DB (HgDB) zinterpretuje takie pole jako String
. Jeżeli później zaktualizujemy to pole do łańcucha znakowego powyżej 64 znaków, to zostanie to zinterpretowane jako Text
- a tym samym nastąpi zmiana definicji pola, a w konsekwencji całej definicji metadanych sprawy (typu sprawy) co może prowadzić do utworzenia nowej wersji sprawy. Więc aby uniknąć takiej sytuacji, jeżeli wiesz, że pole może zawierać więcej niż 64 znaki to warto zawsze ustawić atrybut type na Text
.
W jaki sposób minimalizować ryzyko NIEŚWIADOMYCH zmian wersji spraw?
Przesyłaj jak najwięcej danych dotyczących definicji typu razem ze sprawą
Przede wszystkim należy przesyłać (O ILE TO MOŻLIWE) systemowi jak najwięcej danych dotyczących definicji typu sprawy wraz ze sprawą. Na przykład, gdy wykorzystujemy usługi SOAP w przesyłanym XML sprawy warto dodać atrybuty "type" oraz "position" - obydwa atrybuty są opcjonalne, ale bardzo pomagają w jednoznacznej interpretacji definicji pola sprawy (przykład):
Dobrze | LEPIEJ |
---|---|
<dictionaryName>TEST</dictionaryName> <name>Test Name3</name> <code>Test Code3</code> | <dictionaryName position="1" type="String">TEST</dictionaryName> <name position="2" type="String">Test Name3</name> <code position="3" type="String">Test Code3</code> |
Ustaw parametr konfiguracji blokujący tworzenie nowych wersji
Kolejną możliwością jest wykorzystanie blokującego zmianę wersji sprawy parametru konfiguracyjnego serwera oraz parametru żądania przesyłane wraz kontekstem podczas zapisu. Od wersji 3.0.2.x został wprowadzony parametr konfiguracyjny serwera Mercury DB (HgDB) o nazwie mercury.saveRequestContext.forceChangeType.default
. W pliku konfiguracji mercury.properties
należy wstawić:
mercury.saveRequestContext.forceChangeType.default=false
Taka konfiguracja zablokuje zmiany wersji spraw podczas ich aktualizacji na poziomie operacji zapisu. Nie oznacza to jednak, że blokujemy tę operację na stałe. Wartość parametru jest wartością domyślną, jaka ma być zastosowana podczas zapisu. Jego wartość może być nadpisana odpowiednią wartością parametru przesłanego w kontekście samego żądania zapisu. Wystarczy dodać parametr saveRequestContext.forceChangeType
z odpowiednią wartością (true
lub false
) do przesyłanego obiektu context
aby odblokować albo zablokować, automatyczną zmianę wersji sprawy np.:
<requestProperties> <entry> <key>saveRequestContext.forceChangeType</key> <value>true</value> </entry> </requestProperties>
Zmiana wersji sprawy a Katalog spraw
Jak wspomniano w rozdziale "Konsekwencje zmiany wersji sprawy", jedną z wad rozwiązania jest to, że w przypadku zmiany wersji sprawy następuje utrata powiązania pomiędzy sprawą nadrzędną a najnowszą wersją sprawy podrzędnej. Kwestie zależności spraw złożonych (obiektów złożonych) zostały opisane w artykule Sprawa vs. obiekt. Inną rzeczą jest organizacja Katalogu spraw. Katalog spraw (oparty o encję Case2Case) to związki pomiędzy sprawami (niezależnie od ich definicji) w postaci drzewa. Tworzone one są w oparciu o wartość pola rootVersionId
co sprawa, że tworzone powiązanie zawsze wskazuje na powiązanie pomiędzy ostatnimi wersjami danych spraw.
Inne wykorzystanie mechanizmu tworzenia wersji spraw
Trendem w obecnych czasach jest elektronizacja wszelkiego rodzaju dokumentów. Mercury DB (HgDB) jest wręcz idealnym narzędziem do przechowywania takich danych, dostarczając bogate API składowania jak i wyszukiwania danych. Bardzo dobrym przykładem wykorzystania systemu jest składowanie danych związanych ze wszelkiego rodzaju umowami, z którymi powiązane są adresy stron ją zawierających. Oczywiście możemy tak zdefiniować obiekt umowy, w którym pole adresu nie stanowi osobnej, podrzędnej sprawy, ale narażamy się wtedy na duplikację danych, podczas wprowadzania których narażamy się również na błędy. Dlatego najczęściej obiekty adresów przechowywane są jako osobne sprawy i tworzone jest powiązanie pomiędzy sprawą umowy a tymże adresem. I pojawia się dylemat, co zrobić, gdy zmienia się nazwa ulicy? Jeżeli operujemy na instancji adresu, to jego zmiana, poprzez związek, również wpływa na umowę, a taka sytuacja jest niedopuszczalna z punktu widzenia biznesowego, bo zmiana adresu wymaga aneksu (innej umowy) do umowy podstawowej.
Aby zachować spójność biznesową można wykorzystać mechanizm tworzenia nowych wersji spraw tak aby umowa była powiązana ze starą wersją adresu, a aneks już z nową. I to co wcześniej było postrzegane jako wada okazuje się zaletą.
Wcześniej pisaliśmy, że nowa wersja sprawy tworzona jest podczas zmiany jej typu, ale od wersji 3.0.1.0.2 można wymusić utworzenie nowej wersji sprawy przy zachowaniu tej samej definicji metadanych sprawy. Aby to zrobić należy wykonać następujące kroki:
- Pobrać sprawę której wersję chcemy zmienić (oryginał).
- Zrobić kopię jej obiektu (kopia).
- W oryginale zmieniamy:
- pole nagłówka
status
na 'Z' (zamknięta), - pole nagłówka
dirty
na 'true'
- pole nagłówka
- W kopii ustawiamy:
- pole nagłówka
previousVersionId
ustawiamy na wartość z polacaseId
- pole nagłówka
caseId
ustawiamy na wartość pustą - pole nagłówka
dirty
na 'true' - Dokonujemy zmiany parametrów, które mają ulec zmianie
- pole nagłówka
- Tworzymy listę spraw:
- na pierwszej pozycji ustawiamy oryginał
- na drugiej pozycji ustawiamy kopię
- Wysyłamy listę do zapisu
W ten sposób utworzona zostanie nowa wersja sprawy.
Zmiana wersji sprawy z wykorzystaniem klienta hgdb-client-open (Java)
Poniżej przykład implementacji zmiany wersji sprawy:
MrcObject mrcObject; MrcObject mrcObjectCopy; SaveRequestContext.FORCE_CHANGE_TYPE.setContextValue(context, "false"); ICaseBusiness caseBusiness = (ICaseBusiness) getRegistry().getContextBean(ICaseBusiness.class); /* Pobranie oryginalnej sprawy z bazy danych */ mrcObject = caseBusiness.find(context, /*caseId */ 12345L); assertNotNull(mrcObject); /* Pobranie instancji nagłówka pobranej sprawy */ /* utworzenie kopii oryginalnej sprawy - START */ mrcObjectCopy = mrcObject instanceof MrcObjectWithRequiredPosition ? new MrcObjectWithRequiredPosition() : new MrcObject(); mrcObjectCopy.copy(context, mrcObject); assertTrue("Obiekty powinny być takie same", mrcObject.equals(mrcObjectCopy)); /* utworzenie kopii oryginalnej sprawy - KONIEC */ /* Stara wersja sprawy - START */ MrcCaseHeader origHeader = (MrcCaseHeader) mrcObject.getPropertyValue("mrcCaseHeader"); /* Pobranie identyfikatora oryginalnej sprawy */ Long origCaseId = origHeader.getCaseId(); /* ustawiam sprawę starą na zamkniętą */ origHeader.setStatus(Case.State.Z); origHeader.setDirty(Boolean.TRUE); /* Stara wersja sprawy - KONIEC */ /* Nowa wersja sprawy - START */ /* Pobranie instancji nagłówka kopii */ MrcCaseHeader copygHeader = (MrcCaseHeader) mrcObjectCopy.getPropertyValue("mrcCaseHeader"); /* nowej wersji sprawy ustawiam caseId na null */ copygHeader.setCaseId(null); /* ustawiam wskazanie nowej wersji na poprzednią */ copygHeader.setPreviousVersionId(origCaseId); copygHeader.setDirty(Boolean.TRUE); /* Nowa wersja sprawy - KONIEC */ /** Tworzę listę spraw do zapisu - START */ /* na liście na pierwszej pozycji będzie stara wersja sprawy na, drugiej nowa */ MrcObjectMetadata metadata = new MrcObjectMetadata(); metadata.setClassName(copygHeader.getTypeCode()); metadata.setStatus(MrcCaseStatus.ALL); MrcList saveList = new MrcList(metadata); saveList.addArrayData(mrcObject); saveList.addArrayData(mrcObjectCopy); /** Tworzę listę spraw do zapisu - KONIEC */ /* Realizacja zapisu listy spraw */ MrcList savedList = caseBusiness.saveList(context, saveList, /* forceAddStore2Type */ true);
Kroki zmiany wersji sprawy za pomocą usług SOAP
Poniżej przedstawiono ilustracje wywołania poszczególnych kroków zmiany wersji sprawy w projekcie SoapUI. Wykorzystano usługę CaseBusinessAction, której WSDL dostępny jest pod adresem [https|https]://<nazwa_serwera>[:port]/mercury-ws-app/services/CaseBusinessAction?wsdl zainstalowanego produktu Mercury DB (HgDB).
- Pobranie istniejącej sprawy za pomocą metody findXML:
Kopiuję wynikowy XML do notatnika i tworzę XML listy spraw do zapisu dokonując następujących zmian:
- (sprawa oryginalna) linia 8: zamiana na
<status type="String">Z</status>
- (sprawa oryginalna) linia 24: zmiana na
<dirty type="Boolean">true</dirty>
- (kopia sprawy) linia 33: zamiana na
<caseId type="Integer"></caseId>
- (kopia sprawy) linia 38: dodanie, ustawienie
<previousVersionId type="Integer">118906</previousVersionId>
- (kopia sprawy) linia 54: zmiana na
<dirty type="Boolean">true</dirty>
- (sprawa oryginalna) linia 8: zamiana na
<variable type="TestSimpleDictionary[]"> <item type="TestSimpleDictionary" withRequiredPosition="true"> <mrcCaseHeader type="MrcCaseHeader"> <caseId type="Integer">118906</caseId> <groupId type="Integer">54504</groupId> <typeId type="Integer">54404</typeId> <typeCode type="String">TestSimpleDictionary</typeCode> <status type="String">Z</status> <rootVersionId type="Integer">118906</rootVersionId> <priceValue type="Decimal">0.0</priceValue> <storeCount type="Integer">1</storeCount> <storeId type="Integer">52404</storeId> <createDate type="Date">2020-09-14 14:13:10</createDate> <createdBy type="String">slawas</createdBy> <lastModifyDate type="Date">2020-09-14 14:13:10</lastModifyDate> <lastModifiedBy type="String">slawas</lastModifiedBy> <modifyComment type="String">SOAP request</modifyComment> <createdByRoleName type="String">CKBPM-Team</createdByRoleName> <lastModifiedByRoleName type="String">CKBPM-Team</lastModifiedByRoleName> <className type="String">TestSimpleDictionary</className> <objectID type="String">?.TestSimpleDictionary</objectID> <rootVersionContextID type="String">SoapUI.001</rootVersionContextID> <version type="String">4509</version> <dirty type="Boolean">true</dirty> <pkPropertyName type="String">dictionaryName||'.'||code</pkPropertyName> </mrcCaseHeader> <dictionaryName position="1" type="String">TEST</dictionaryName> <name position="2" type="String">Test Name4</name> <code position="3" type="String">Test Code4</code> </item> <item type="TestSimpleDictionary" withRequiredPosition="true"> <mrcCaseHeader type="MrcCaseHeader"> <caseId type="Integer"></caseId> <groupId type="Integer">54504</groupId> <typeId type="Integer">54404</typeId> <typeCode type="String">TestSimpleDictionary</typeCode> <status type="String">A</status> <previousVersionId type="Integer">118906</previousVersionId> <rootVersionId type="Integer">118906</rootVersionId> <priceValue type="Decimal">0.0</priceValue> <storeCount type="Integer">1</storeCount> <storeId type="Integer">52404</storeId> <createDate type="Date">2020-09-14 14:13:10</createDate> <createdBy type="String">slawas</createdBy> <lastModifyDate type="Date">2020-09-14 14:13:10</lastModifyDate> <lastModifiedBy type="String">slawas</lastModifiedBy> <modifyComment type="String">SOAP request</modifyComment> <createdByRoleName type="String">CKBPM-Team</createdByRoleName> <lastModifiedByRoleName type="String">CKBPM-Team</lastModifiedByRoleName> <className type="String">TestSimpleDictionary</className> <objectID type="String">?.TestSimpleDictionary</objectID> <rootVersionContextID type="String">SoapUI.001</rootVersionContextID> <version type="String">4509</version> <dirty type="Boolean">true</dirty> <pkPropertyName type="String">dictionaryName||'.'||code</pkPropertyName> </mrcCaseHeader> <dictionaryName position="1" type="String">TEST</dictionaryName> <name position="2" type="String">Test Name4</name> <code position="3" type="String">Test Code4</code> </item> </variable>
- Wysłanie przygotowanych danych do zapisu za pośrednictwem metody saveXML:
- Jak ilustruje przykład utworzona została nowa wersja instancji obiektu sprawy.