dimesum

Startsida / Blogg / Pengar

Pengar

Varför en ändrad utgift måste ange delningen på nytt

· 8 min läsning ·

En ändrings standardvärden är pengabuggar, så Dimesum gör participants och split_type obligatoriska på varje PATCH av en utgift, vid sidan av base_version som avvisar en föråldrad ändring i stället för att slå ihop den.

Att rätta ett stavfel ska inte ändra vem som är skyldig pengar. I Dimesum anger en ändring av en delad utgift hela delningen på nytt. participants och split_type är obligatoriska på varje PATCH, och en ändring som utelämnar något av dem kommer tillbaka 400 i stället för att fylla i ett standardvärde. Standardvärdena vid skapande är alla nuvarande medlemmar och en lika delning, vilket vid en ändring är pengabuggar.

Två fel gör saken konkret. En inneboende som gick med i augusti dras in i julis middag, eftersom "alla nuvarande medlemmar" utvärderas när ändringen landar, inte när utgiften skrevs. En medveten 70/30-delning av hyran plattas ut till 50/50, eftersom ett saknat split_type betyder EQUAL. Inget av felen ger ett felmeddelande, och båda flyttar riktiga pengar.

Hållningen

En ändrings standardvärden är pengabuggar. Ett standardvärde vid skapande gissar om gruppen som författaren ser framför sig just nu. En ändring kommer senare, mot en grupp som har rört sig, och samma gissning skriver tyst om vad människor är skyldiga.

Standardvärden vid skapande beskriver en ny utgift, inte en gammal

Vid skapandet är standardvärdena ärliga. Författaren ser gruppen som den ser ut, och en lika delning mellan alla är det vanliga fallet, så Dimesum fyller i båda. Utgiften lagrar sina indata snarare än enbart sina resultat: splits.percent_bp, splits.weight och splits.exact_minor behåller det som användaren skrev, så att en senare ändring kan öppna den igen.

En ändring är en annan handling. Samma utgift kan rättas flera veckor senare, när en ny inneboende har gått med eller en spökmedlem har gjorts anspråk på. Medlemskapet är ett rörligt mål och delningen är det inte. Att återanvända standardvärdena vid skapande ber dagens grupp att svara på en fråga som den gamla utgiften redan besvarade.

Två versioner av samma ändring: ärvda standardvärden ändrar varje andel, medan en delning som anges på nytt ändrar bara beskrivningen ÄNDRINGEN ÄRVER STANDARDVÄRDENA VID SKAPANDE ÄNDRINGEN ANGER DELNINGEN PÅ NYTT Hyra, 20 000 kronor, juli Hyra, 20 000 kronor, juli v1 Asha 70%, 14 000 Bhavna 30%, 6 000 v1 Asha 70%, 14 000 Bhavna 30%, 6 000 Chetan flyttar in i augusti. Chetan flyttar in i augusti. PATCH rättar ett stavfel, skickar ingen delning. PATCH rättar ett stavfel, skickar delningen: 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 är skyldig för en månad han aldrig bodde här. Bara beskrivningen ändrades.
Samma ändring på ett tecken, två gånger. Att ärva standardvärdena vid skapande delar om 20 000 kronor på tre och debiterar en medlem som gick med en månad senare; att ange delningen på nytt lämnar varje andel orörd.

Vägran att gissa är inte något nytt här. En utgift med flera betalare måste ange sina betalare på nytt också. soleStoredPayer återanvänder den lagrade betalaren bara när utgiften har exakt en, så en ändring som förblir tyst om två betalare avvisas i stället för att tilldelas om. En ändring som inte säger något om betalare behåller utgiftens egen betalare, aldrig personen som gör ändringen.

API:et avvisar en ändring som inte anger sin delning på nytt

Kontrollen i gatewayen körs innan några pengar beräknas. När participants är tom eller split_type är blank kommer förfrågan tillbaka 400 med koden invalid_expense och meddelandet "an edit must restate the split: participants and split_type are required". Utgiftstjänsten upprepar regeln i validateAmend, så en anropare som når tjänsten på ett annat sätt möter samma vägran.

Vad ett skapande skickar jämfört med vad en ändring måste ange på nytt, på POST och PATCH /v1/groups/{id}/expenses.
FältVid skapandeVid en ändringVad standardvärdet skulle kosta
participantsValfritt. Standard är alla nuvarande medlemmarObligatorisktEn medlem som gick med efteråt hamnar i en gammal utgift
split_typeValfritt. Standard är EQUALObligatorisktEn 70/30-delning plattas ut till 50/50
payersValfritt. Standard är författarenUtelämnad behåller utgiftens enda betalare; två betalare måste anges på nyttDen som ändrar blir betalare, vilket vänder på vem som är skyldig vem
base_versionSkickas inteObligatoriskt, och måste vara lika med den aktuella versionenEn föråldrad ändring skriver över en ändring som dess författare aldrig läste
revision_idKlientens UUIDv7, idempotensnyckelnSamma, en per versionEn omförsökt ändring debiterar gruppen två gånger
currencyAnges per utgiftMåste stämma; en ändring avvisasEn saldorad håller en valuta per medlem

Valuta hör till samma familj av vägranden. En ändring kan inte omvandla en utgift till en annan valuta, eftersom en saldorad håller en valuta per medlem. Skrivsidan avvisar ändringen, och den append-only-bokföringen parkerar ett sådant tillägg om det någonsin når den. Att vägra vid dörren håller de två halvorna från att vara oense.

En föråldrad ändring avvisas åt sin författare, aldrig sammanslagen

base_version är den andra halvan av kontraktet. Varje PATCH bär med sig den version som dess författare läste, och checkTransition jämför den mot raden som transaktionen just låste. Lika, och ändringen tillämpas på version plus ett. Olika, och anroparen får HTTP 409 med koden stale_version.

Att slå ihop är det frestande alternativet, och det är fel. Två ändringar av en utgift är två fullständiga utsagor om vad räkningen betyder. Att slå ihop dem ger en tredje utsaga som ingen skrev, med andelar som ingen av författarna skulle känna igen. Vägran lämnar konflikten tillbaka till den enda person som kan lösa den.

Två författare läser version 3; den första ändringen bokför version 4 och den andra avvisas med 409 stale_version utgift, version 3 Författare A PATCH base_version: 3 200 OK, version 4 Författare B PATCH base_version: 3 409 stale_version Bokföring, en transaktion: EXPENSE_REVERSAL of v3 EXPENSE v4 Författare B läser om version 4, tillämpar ändringen på nytt, skickar base_version: 4
Optimistisk samtidighet på en utgift. Den förlorande ändringen lämnas tillbaka till sin författare med versionen den måste byggas om på. Den vinnande ändringen når bokföringen som en återföring plus en ny bokning, aldrig en uppdatering.

Idempotens och samtidighet hålls medvetet isär. Insättningen av revisionen körs före versionskontrollen, eftersom en omförsökt ändring bär med sig den basversion den ursprungligen läste, som nu är föråldrad. Ett omtag måste läsas som ett omtag snarare än en konflikt, så revision_id svarar först och returnerar det lagrade resultatet.

Svaret bär redan med sig det som nästa ändring behöver

Att kräva fler fält på en PATCH är rimligt bara om en klient kan få tag på dem billigt. Varje svar för en utgift bär med sig version, så en klient som just skrev en utgift kan ändra den utan en andra läsning. Svaret vid skapande, svaret vid ändring och varje rad i listan bär med sig samma fält.

För klienten som inte just skrev utgiften returnerar GET /v1/groups/{id}/expenses/{id} versionen, de beräknade andelarna och indata bakom dem. Indata kommer tillbaka under samma namn som en PATCH accepterar: participants, split_type, percents, weights, shares, items, pools. En klient läser en form och skickar tillbaka den med ändringar, i stället för att översätta mellan två vokabulär för en räkning.

GET-svaret och PATCH-förfrågan använder samma fältnamn, med version omdöpt till base_version GET RETURNERAR PATCH ACCEPTERAR version base_version participants participants split_type split_type percents, weights, shares percents, weights, shares items, pools items, pools omdöpt
Ett vokabulär, två riktningar. Bara versionsfältet byter namn under rundresan, så en ändringsklient underhåller aldrig ett översättningslager mellan det den läser och det den skriver.

Symmetrin betyder mest för delningar som inte kan rekonstrueras. En PERCENT-delning eller en specificerad räkning kan inte anges på nytt utifrån enbart sina uträknade andelar, eftersom avrundning redan har tillämpats och baspunkterna och radposterna är borta. Revisionsögonblicksbilder bär med sig indata också, så historikbladet kan visa vad som specificerades på vilken tidigare version som helst.

Obligatoriskt nu, eftersom ett krav inte kan läggas till senare

Att kräva ett fält på dag ett är ett beslut om framtiden snarare än om i dag. Att luckra upp ett obligatoriskt fält senare är bakåtkompatibelt: klienterna skickar det redan, och servern börjar acceptera förfrågningar utan det. Att lägga till ett krav senare bryter varje klient som förlitade sig på det gamla standardvärdet.

Så riktningen väljs en gång, tidigt. Dimesum kräver participants, split_type och base_version på en ändring medan antalet klienter fortfarande är litet nog att ändra. Om ett säkert standardvärde för ändringar någonsin hittas blir fälten valfria och inget som redan levererats slutar fungera.

Låt en ändring ange vad den betyder

Standardvärden hör till skapandet, där författaren kan se gruppen de går med på. Vid en ändring är samma standardvärden en gissning om en grupp som sedan har rört sig. Ditt nästa steg: öppna din egen läsändpunkt och kontrollera att den lämnar tillbaka delningens indata under exakt de fältnamn som din skrivändpunkt accepterar. En klient som måste översätta mellan de två kommer till slut att översätta en av dem fel.

Vanliga frågor

Varför måste jag ange participants och split_type när jag ändrar en delad utgift?

Dimesum kräver båda fälten eftersom standardvärdena vid skapande är fel för en ändring. Vid skapande utgår Dimesum från alla nuvarande medlemmar och en lika delning, vilket stämmer med gruppen som författaren ser framför sig. En ändring kan komma flera veckor senare, efter att någon har gått med. Att återanvända de standardvärdena skulle dra in en ny inneboende i en gammal middag och platta ut en medveten 70/30-delning av hyran tillbaka till 50/50, utan att något fel visas.

Vad händer om två personer ändrar samma utgift samtidigt?

Den andra ändringen avvisas med HTTP 409 och felkoden stale_version. Varje PATCH bär med sig base_version, den version som författaren läste, och ändringsvägen jämför den mot den låsta raden. En avvikelse betyder att utgiften har rört sig, så ändringen kommer tillbaka för att författaren ska tillämpa den på nytt mot den version de nu kan se. Ingenting slås ihop.

Uppdateras bokföringsraderna direkt när jag ändrar en utgift?

Nej, att ändra en utgift uppdaterar aldrig en bokföringsrad i Dimesum. En ändring bokför två verifikat i en transaktion: en EXPENSE_REVERSAL som upphäver den gamla versionen ben för ben, sedan ett EXPENSE-verifikat för den nya versionen. UPDATE och DELETE är återkallade från bokföringens egen databasroll, så en mutation är omöjlig även för buggig kod. Du ser en markering om att utgiften ändrats och ett historikblad.

Behöver jag göra ett extra API-anrop innan jag ändrar en delad utgift?

Nej, en klient som just skrev utgiften har redan den version som en ändring behöver. Varje svar för en utgift bär med sig version, vilket är det som nästa PATCH skickar som base_version. En klient som inte skrev utgiften anropar GET /v1/groups/{id}/expenses/{id}, som returnerar versionen plus delningsuppgifterna under samma fältnamn som en PATCH accepterar.

Varför kräva participants och split_type nu i stället för att lägga till dem senare?

Att kräva ett fält på dag ett är omvändbart, medan att lägga till ett senare inte är det. Att luckra upp ett obligatoriskt fält senare är bakåtkompatibelt: klienterna skickar det redan, och servern börjar acceptera förfrågningar utan det. Att lägga till ett krav senare bryter varje klient som förlitade sig på det gamla standardvärdet, och i ett penning-API är felet tyst tills någons saldo är fel.