dimesum

Strona główna / Blog / Pieniądze

Pieniądze

Edycja wydatku musi na nowo określić podział

· 8 min czytania ·

Domyślne wartości przy edycji to błędy w rozliczeniach, dlatego Dimesum wymaga pól participants i split_type w każdym żądaniu PATCH wydatku, obok base_version, które odrzuca nieaktualną edycję zamiast ją scalać.

Poprawienie literówki nie powinno zmieniać tego, kto komu jest winien pieniądze. W Dimesum edycja wspólnego wydatku na nowo określa cały podział. Pola participants i split_type są wymagane w każdym żądaniu PATCH, a edycja, która pomija którekolwiek z nich, wraca z kodem 400, zamiast uzupełniać wartość domyślną. Domyślne wartości przy tworzeniu to wszyscy obecni członkowie i równy podział, co przy edycji staje się błędem w rozliczeniach.

Dwie usterki pokazują to konkretnie. Współlokator, który dołączył w sierpniu, zostaje dopisany do lipcowej kolacji, ponieważ zwrot „wszyscy obecni członkowie" jest liczony w chwili zapisania edycji, a nie w chwili utworzenia wydatku. Celowy podział czynszu 70/30 spłaszcza się do 50/50, ponieważ brak pola split_type oznacza EQUAL. Żadna z tych usterek nie zgłasza błędu, a obie przesuwają prawdziwe pieniądze.

Stanowisko

Domyślne wartości przy edycji to błędy w rozliczeniach. Wartość domyślna z chwili tworzenia zgaduje na temat grupy, którą autor widzi w tym momencie. Edycja przychodzi później, wobec grupy, która się zmieniła, i to samo zgadywanie po cichu zmienia to, ile kto jest winien.

Wartości domyślne przy tworzeniu opisują nowy wydatek, nie stary

W chwili tworzenia wartości domyślne są uczciwe. Autor patrzy na grupę w jej obecnym kształcie, a równy podział na wszystkich jest częstym przypadkiem, więc Dimesum uzupełnia oba pola. Wydatek zapisuje swoje dane wejściowe, a nie tylko wyniki: pola splits.percent_bp, splits.weight i splits.exact_minor zachowują to, co wpisał użytkownik, dzięki czemu późniejsza edycja może je otworzyć na nowo.

Edycja to inna czynność. Ten sam wydatek można poprawić kilka tygodni później, gdy dołączy nowy współlokator albo ktoś przejmie konto widmo. Skład grupy się zmienia, a podział nie. Ponowne użycie domyślnych wartości z chwili tworzenia każe dzisiejszej grupie odpowiadać na pytanie, na które stary wydatek już odpowiedział.

Dwie wersje tej samej edycji: odziedziczone wartości domyślne zmieniają każdy udział, a ponownie określony podział zmienia tylko opis EDYCJA DZIEDZICZY WARTOŚCI DOMYŚLNE EDYCJA NA NOWO OKREŚLA PODZIAŁ Czynsz, 20,000 zł, lipiec Czynsz, 20,000 zł, lipiec v1 Asha 70%, 14,000 Bhavna 30%, 6,000 v1 Asha 70%, 14,000 Bhavna 30%, 6,000 Chetan dołącza do mieszkania w sierpniu. Chetan dołącza do mieszkania w sierpniu. PATCH poprawia literówkę, nie wysyła podziału. PATCH poprawia literówkę, wysyła podział: participants: Asha, Bhavna split_type: PERCENT 7000 / 3000 v2 Asha 6,666.67 Bhavna 6,666.67 Chetan 6,666.66 v2 Asha 70%, 14,000 Bhavna 30%, 6,000 Chetan jest winien za miesiąc, w którym nie mieszkał. Zmienił się tylko opis.
Ta sama jednoznakowa edycja, dwa razy. Odziedziczenie domyślnych wartości dzieli 20,000 zł na trzy części i obciąża członka, który dołączył miesiąc później; ponowne określenie podziału pozostawia każdy udział nietknięty.

Odmowa zgadywania nie jest tu niczym nowym. Wydatek z wieloma płatnikami również musi na nowo określić swoich płatników. soleStoredPayer używa ponownie zapisanego płatnika tylko wtedy, gdy wydatek ma dokładnie jednego, więc edycja, która milczy o dwóch płatnikach, jest odrzucana, zamiast przypisywać ich na nowo. Edycja, która nic nie mówi o płatnikach, zachowuje własnego płatnika wydatku, nigdy osoby dokonującej edycji.

API odrzuca edycję, która nie określa na nowo swojego podziału

Sprawdzenie w bramie odbywa się, zanim policzone zostaną jakiekolwiek pieniądze. Gdy pole participants jest puste lub split_type jest niewypełnione, żądanie wraca z kodem 400, kodem invalid_expense i komunikatem „an edit must restate the split: participants and split_type are required". Usługa wydatków powtarza tę regułę w validateAmend, więc wywołujący, który dotrze do usługi inną drogą, spotyka tę samą odmowę.

Co wysyła tworzenie, a co edycja musi określić na nowo, w żądaniach POST i PATCH /v1/groups/{id}/expenses.
PolePrzy tworzeniuPrzy edycjiIle kosztowałaby wartość domyślna
participantsOpcjonalne. Domyślnie wszyscy obecni członkowieWymaganeCzłonek, który dołączył później, trafia do starego wydatku
split_typeOpcjonalne. Domyślnie EQUALWymaganePodział 70/30 spłaszcza się do 50/50
payersOpcjonalne. Domyślnie autorPominięcie zachowuje jedynego płatnika wydatku; dwóch płatników trzeba określić na nowoEdytujący staje się płatnikiem, odwracając to, kto komu jest winien
base_versionNie jest wysyłaneWymagane i musi być równe bieżącej wersjiNieaktualna edycja nadpisuje zmianę, której jej autor nigdy nie przeczytał
revision_idUUIDv7 od klienta, klucz idempotentnościTo samo, jeden na wersjęPonowiona edycja obciąża grupę dwa razy
currencyPodawana dla każdego wydatkuMusi się zgadzać; zmiana jest odrzucanaWiersz salda przechowuje jedną walutę na członka

Waluta należy do tej samej rodziny odmów. Edycja nie może zmienić waluty wydatku, ponieważ wiersz salda przechowuje jedną walutę na członka. Strona zapisu odrzuca zmianę, a księga tylko do dopisywania odkłada taką poprawkę na bok, gdyby kiedykolwiek do niej dotarła. Odmowa już przy wejściu sprawia, że obie połowy się nie rozjeżdżają.

Nieaktualna edycja jest odrzucana do jej autora, nigdy scalana

base_version to druga połowa kontraktu. Każdy PATCH niesie wersję, którą przeczytał jego autor, a checkTransition porównuje ją z wierszem, który transakcja właśnie zablokowała. Gdy są równe, edycja stosuje się na wersji o jeden wyższej. Gdy się różnią, wywołujący otrzymuje HTTP 409 z kodem stale_version.

Scalanie to kusząca alternatywa i jest błędne. Dwie edycje jednego wydatku to dwa kompletne stwierdzenia tego, co oznacza rachunek. Ich scalenie tworzy trzecie stwierdzenie, którego nikt nie napisał, z udziałami, których żaden z autorów by nie rozpoznał. Odrzucenie oddaje konflikt z powrotem jednej osobie, która potrafi go rozwiązać.

Dwóch autorów czyta wersję 3; pierwsza edycja zapisuje wersję 4, a druga zostaje odrzucona z 409 stale_version wydatek, wersja 3 Autor A PATCH base_version: 3 200 OK, wersja 4 Autor B PATCH base_version: 3 409 stale_version Księga, jedna transakcja: EXPENSE_REVERSAL of v3 EXPENSE v4 Autor B ponownie czyta wersję 4, ponownie stosuje zmianę, wysyła base_version: 4
Optymistyczna współbieżność na wydatku. Przegrywająca edycja wraca do swojego autora wraz z wersją, na której trzeba ją odbudować. Zwycięska edycja dociera do księgi jako odwrócenie i ponowny zapis, nigdy jako aktualizacja.

Idempotentność i współbieżność są rozdzielone celowo. Wstawienie rewizji odbywa się przed sprawdzeniem wersji, ponieważ ponowiona edycja niesie wersję bazową, którą pierwotnie przeczytała, a ta jest już nieaktualna. Powtórzenie musi zostać odczytane jako powtórzenie, a nie jako konflikt, więc revision_id odpowiada jako pierwsze i zwraca zapisany wynik.

Odpowiedź już niesie to, czego potrzebuje następna edycja

Wymaganie większej liczby pól w żądaniu PATCH jest uczciwe tylko wtedy, gdy klient może je zdobyć tanio. Każda odpowiedź dotycząca wydatku niesie version, więc klient, który właśnie zapisał wydatek, może go edytować bez drugiego odczytu. Odpowiedź tworzenia, odpowiedź edycji i każdy wiersz listy niosą to samo pole.

Dla klienta, który nie zapisał właśnie wydatku, GET /v1/groups/{id}/expenses/{id} zwraca wersję, wyliczone udziały oraz dane wejściowe, które za nimi stoją. Dane wejściowe wracają pod tymi samymi nazwami, które przyjmuje PATCH: participants, split_type, percents, weights, shares, items, pools. Klient czyta jeden kształt i odsyła go z edycjami, zamiast tłumaczyć między dwoma słownikami dla jednego rachunku.

Odpowiedź GET i żądanie PATCH używają tych samych nazw pól, przy czym version zmienia nazwę na base_version GET ZWRACA PATCH PRZYJMUJE version base_version participants participants split_type split_type percents, weights, shares percents, weights, shares items, pools items, pools zmieniona nazwa
Jeden słownik, dwa kierunki. Tylko pole version zmienia nazwę w obie strony, więc klient edytujący nigdy nie utrzymuje warstwy tłumaczącej między tym, co czyta, a tym, co zapisuje.

Symetria ma największe znaczenie dla podziałów, których nie da się odtworzyć. Podziału PERCENT ani rachunku z pozycjami nie da się określić na nowo z samych wyliczonych udziałów, ponieważ zaokrąglenie zostało już zastosowane, a punkty bazowe i pozycje rachunku zniknęły. Migawki rewizji niosą także dane wejściowe, więc arkusz historii może pokazać, co zostało rozpisane na pozycje w dowolnej przeszłej wersji.

Wymagane teraz, ponieważ wymogu nie da się dodać później

Wymaganie pola od pierwszego dnia to decyzja o przyszłości, a nie o dniu dzisiejszym. Późniejsze złagodzenie wymaganego pola jest wstecznie zgodne: klienci już je wysyłają, a serwer zaczyna przyjmować żądania bez niego. Późniejsze dodanie wymogu psuje każdego klienta, który polegał na starej wartości domyślnej.

Dlatego kierunek wybiera się raz, wcześnie. Dimesum wymaga participants, split_type i base_version przy edycji, dopóki liczba klientów jest jeszcze na tyle mała, by można było ją zmienić. Jeśli kiedyś znajdzie się bezpieczna wartość domyślna dla edycji, pola staną się opcjonalne i nic, co już zostało wdrożone, nie przestanie działać.

Niech edycja na nowo określa to, co oznacza

Wartości domyślne należą do tworzenia, gdzie autor widzi grupę, na którą się zgadza. Przy edycji te same wartości domyślne są zgadywaniem na temat grupy, która od tamtej pory się zmieniła. Twój następny krok: otwórz własny punkt odczytu i sprawdź, czy oddaje dane wejściowe podziału pod dokładnie tymi nazwami pól, które przyjmuje twój punkt zapisu. Klient, który musi tłumaczyć między tymi dwoma, prędzej czy później przetłumaczy jedno z nich błędnie.

Często zadawane pytania

Dlaczego edycja wspólnego wydatku wymaga participants i split_type?

Dimesum wymaga obu pól, ponieważ domyślne wartości z tworzenia są błędne przy edycji. Przy tworzeniu Dimesum przyjmuje domyślnie wszystkich obecnych członków i równy podział, co odpowiada grupie, na którą patrzy autor. Edycja może pojawić się kilka tygodni później, gdy ktoś już dołączył. Ponowne użycie tych wartości domyślnych wciągnęłoby nowego współlokatora do starej kolacji i spłaszczyło celowy podział czynszu 70/30 z powrotem do 50/50, bez żadnego pokazanego błędu.

Co się dzieje, gdy dwie osoby edytują ten sam wydatek naraz?

Druga edycja zostaje odrzucona z HTTP 409 i kodem błędu stale_version. Każdy PATCH niesie base_version, wersję, którą przeczytał jego autor, a ścieżka edycji porównuje ją z zablokowanym wierszem. Niezgodność oznacza, że wydatek się zmienił, więc edycja wraca do autora, by zastosował ją ponownie wobec wersji, którą teraz widzi. Nic nie jest scalane.

Czy edycja wydatku aktualizuje wiersze księgi w miejscu?

Nie, edycja wydatku nigdy nie aktualizuje wiersza księgi w Dimesum. Edycja księguje dwa zapisy w jednej transakcji: EXPENSE_REVERSAL, który znosi starą wersję pozycja po pozycji, a następnie zapis EXPENSE dla nowej wersji. Operacje UPDATE i DELETE są odebrane własnej roli bazodanowej księgi, więc zmiana jest niemożliwa nawet dla wadliwego kodu. Widzisz oznaczenie edytowania oraz arkusz historii.

Czy przed edycją wspólnego wydatku potrzebuję drugiego wywołania API?

Nie, klient, który właśnie zapisał wydatek, już ma wersję, której potrzebuje edycja. Każda odpowiedź dotycząca wydatku niesie version, które następny PATCH wysyła jako base_version. Klient, który nie zapisał wydatku, wywołuje GET /v1/groups/{id}/expenses/{id}, co zwraca wersję oraz dane wejściowe podziału pod tymi samymi nazwami pól, które przyjmuje PATCH.

Dlaczego wymagać participants i split_type teraz, a nie dodać ich później?

Wymaganie pola od pierwszego dnia jest odwracalne, a dodanie go później nie. Późniejsze złagodzenie wymaganego pola jest wstecznie zgodne: klienci już je wysyłają, a serwer zaczyna przyjmować żądania bez niego. Późniejsze dodanie wymogu psuje każdego klienta, który polegał na starej wartości domyślnej, a w API obsługującym pieniądze awaria jest cicha, dopóki czyjeś saldo nie okaże się błędne.