Kahdenkertainen kirjanpito kehittäjille: primitiivit, jotka jokaisen fintech-koodarin kannattaa tuntea
Jos mallinnat rahan yhtenä lukusarakkeena, kadut sitä. Kahdenkertainen kirjanpito (päiväkirja, pääkirja, debet, muuttumattomuus) on fintechin oikea tietomalli.
Tässä on bugiraportti, jonka pitäisi pelottaa sinua: "saldo näkyy oikein dashboardilla mutta väärin exportissa." Olen lukenut jonkin version tuosta lauseesta useammin kuin kehtaan myöntää. Se tarkoittaa lähes aina samaa asiaa. Joku on mallintanut rahan yhtenä lukuna, päivittänyt sitä kolmesta eri koodipolusta, eikä kukaan osaa enää sanoa, mitä luvun oikeastaan pitäisi tarkoittaa.
Kun rakensin Vantnodin pääkirjan ytimen, edessäni oli valinta. Tallentaa tilin saldo sarakkeena ja lisätä tai vähentää sitä sitä mukaa kuin tapahtumia tulee: nopea kirjoittaa, helppo lukea, ilmiselvä. Vai tehdä niin kuin kirjanpitäjät ovat tehneet viisisataa vuotta ja käyttää kahdenkertaista kirjanpitoa. Valitsin kahdenkertaisen, ja haluan käydä kanssasi läpi miksi. Kirjanpidon käsitteet nimittäin kääntyvät siististi tietorakenteiksi ja invarianteiksi, ja kun sen vastaavuuden kerran näkee, lakkaa taistelemasta sitä vastaan.
Yhden sarakkeen ansa
Naiivi malli näyttää viattomalta:
-- versio, jota tulet katumaan
accounts (id, name, balance)
Kirjaat myynnin kreditiksi, hyvityksen debetiksi ja nykäiset balance-saraketta ylös ja alas. Demossa se toimii. Sitten tulee todellisuus vastaan.
Asiakas riitauttaa kahden kuukauden takaisen veloituksen. Mikä saldo oli päivää ennen sitä veloitusta? Et pysty rekonstruoimaan sitä, koska tallensit vain nykyisen luvun, et sitä miten siihen päädyttiin. Niinpä pulttaat päälle transactions-taulun pitämään kirjaa. Nyt sinulla on kaksi totuuden lähdettä: juokseva balance-sarake ja tapahtumien summa. Sinä päivänä kun ne ovat eri mieltä (ja niin tulee käymään, ensimmäisellä kerralla kun kaksi pyyntöä päivittää saman rivin yhtä aikaa, tai kun job ajetaan uudestaan ja kirjaa saman asian kahteen kertaan) et osaa sanoa kumpi valehtelee.
Tämä on se todellinen vikamuoto, eikä se ole teoreettinen. Yhden sarakkeen mallissa ei ole mitään sisäänrakennettua tarkistusta, joka sanoisi "rahaa ei ilmesty eikä katoa." Jokainen kirjoitus on alaston mutaatio, mikään ei pakota kirjanpitoa täsmäämään, joten ennen pitkää se ei täsmää, ja kuulet siitä asiakkaalta etkä omasta järjestelmästäsi.
Kahdenkertainen kirjanpito on tietomalli, ei talousjuttu
Ajatusmalli, josta oli minulle eniten apua: kahdenkertainen kirjanpito ei ole kirjanpitoperinne, jota vain sietelet. Se on tietomalli, jossa on sisäänrakennettu invariantti, ja juuri se invariantti on koko pointti.
Sääntö on yksinkertainen. Jokainen tapahtuma koskettaa vähintään kahta tiliä, ja sisään menevän kokonaissumman täytyy olla yhtä suuri kuin ulos menevän. Rahaa ei koskaan luoda eikä tuhota järjestelmäsi sisällä, se vain liikkuu tililtä toiselle. Tämä on rahan säilymislaki, samaa muotoa kuin fysiikan säilymislait: suure, jonka on pidettävä paikkansa riippumatta siitä mitä reittiä järjestelmä kulkee. Jos olet koskaan kirjoittanut CHECK-rajoitteen tai testin, joka väittää summan olevan nolla, ymmärrät jo moottorin. Sovellat sitä vain rahaan.
Käännetään sanasto, koska kirjanpidon termit pelottavat kehittäjät pakoon, vaikkei niiden pitäisi.
Tilit
Tili on ämpäri, jossa raha voi istua. Pankkitili, myyntisaamiset, myyntituotot, maksettava arvonlisävero. Tietona se on rivi, jolla on id, nimi ja tyyppi. Tyyppi on tärkeä, koska se ratkaisee mikä suunta tarkoittaa "kasvua". Palaan siihen kohta.
accounts (id, name, type, currency)
-- type ∈ asset | liability | equity | income | expense
Debet ja kredit
Tässä on se käsite, johon kaikki kompastuvat, joten tapetaan sekaannus heti. Debet ja kredit eivät ole "miinus" ja "plus". Ne ovat vain kirjauksen kaksi puolta, vasen ja oikea, vuosisatojen vanhaa jargonia, joka tarkoittaa "sarake A ja sarake B".
Se mitä ne tekevät saldolle riippuu tilin tyypistä:
- Vastaaville (asset) ja kuluille (expense): debet kasvattaa, kredit vähentää.
- Veloille, omalle pääomalle ja tuloille (liability, equity, income): kredit kasvattaa, debet vähentää.
Tätä ei tarvitse opetella ulkoa kuin kansanperinnettä. Tallenna se tilin tyypin ominaisuudeksi ja anna koodin soveltaa sitä. Saldoon kohdistuva etumerkillinen vaikutus on puhdas funktio parista (tilin tyyppi, debet vai kredit). Kirjoita se funktio kerran, testaa se, äläkä mieti sitä enää koskaan.
Päiväkirja ja pääkirja
Kaksi sanaa kahdelle rakenteelle, jotka tunnet jo.
Päiväkirja (journal) on lisäävä loki siitä mitä tapahtui, aikajärjestyksessä. Jokaisesta taloudellisesta tapahtumasta tulee päiväkirjavienti, ja päiväkirjavienti on joukko rivejä (debetit ja kreditit), joiden täytyy summautua nollaan. Tämä on tapahtumalokisi. Sitä et koskaan muokkaa.
Pääkirja (ledger) on sama data viipaloituna tileittäin: jokainen rivi, joka kosketti tiettyä tiliä, järjestyksessä, mikä antaa sinulle sen tilin historian ja saldon. Pääkirja on näkymä päiväkirjan päälle, ei erillinen totuuden lähde. Jos olet tehnyt event sourcingia, päiväkirja on tapahtumavirtasi ja pääkirja on projektio. Sama idea, vanhempi nimi.
Skeema, jota oikeasti käytän
Tässä on muoto, riisuttuna olennaiseen:
-- tapahtuma: tasapainossa oleva joukko vientejä
journal_entries (
id, occurred_at, description,
created_at -- milloin kirjasimme sen, ei milloin se tapahtui
)
-- rivit: jokaisella viennillä on kaksi tai useampi
postings (
id,
entry_id references journal_entries(id),
account_id references accounts(id),
direction, -- 'debit' | 'credit'
amount, -- aina positiivinen, kokonaisluku pienimmissä yksiköissä
currency
)
Kolme invarianttia tekee tästä luotettavan, ja ne ovat se varsinainen tuote:
- Jokainen vienti täsmää. Yhden
entry_id:n sisällä debettien summa on yhtä suuri kuin kredittien. Pakota se kirjoitushetkellä, yhden transaktion sisällä: koko vienti kommitoituu tai ei mikään siitä. - Summat ovat positiivisia kokonaislukuja pienimmissä yksiköissä. Tallenna 1050, ei 10,50. Älä koskaan käytä liukulukuja rahaan. Suuntasarake kantaa etumerkin, summasarake suuruuden, eikä liukulukupyöristys syö senttiäkään.
- Päiväkirja on muuttumaton. Ei
UPDATEa, eiDELETEä kirjattuun vientiin. Ei koskaan.
Tuo kolmas ansaitsee oman lukunsa, sillä juuri siinä useimmat itse kyhätyt pääkirjat menevät hiljaa pieleen.
Historiaa ei muokata, se peruutetaan
CRUD-sovelluksista tarttunut vaisto on tämä: tietue on väärin, joten korjataan tietue. Pääkirjassa se vaisto on myrkkyä. Sillä hetkellä kun annat viennin olla muokattavissa, jokaisesta koskaan tekemästäsi raportista tulee valhe, koska luku, jonka näytit jollekulle viime kuussa, ei enää täsmää siihen mitä tietokanta sanoo tänään.
Kirjanpitäjät ratkaisivat tämän jo ennen tietokoneita. Virhettä ei pyyhitä pois, vaan kirjoitetaan peruutusvienti: uusi vienti, joka on väärän peilikuva ja kumoaa sen, ja sitten kirjataan oikea vienti. Väärä vienti jää päiväkirjaan ikuisesti, näkyviin, peruutuksensa viereen. Historia on rehellinen siitä, että virhe tehtiin ja korjattiin.
Vantnodissa tämä on kova sääntö. Kirjatut viennit ovat tietokantatasolla vain luettavissa, ja korjaus on aina uusia päiväkirjavientejä, ei koskaan mutaatio. Hyöty on iso: mikä tahansa saldo minä tahansa päivänä on toistettavissa ajamalla päiväkirja uudelleen kyseiseen päivään asti. Kun asiakas kysyy "mitä kirjanpitoni sanoi 31. maaliskuuta", en arvaa vaan toistan. Tuo yksi ominaisuus on ero pääkirjan, jota voit puolustaa tilintarkastuksessa, ja taulukon, jossa on ylimääräisiä vaiheita, välillä.
Jos et pysty rekonstruoimaan eilistä saldoa pelkästä datastasi, sinulla ei ole pääkirjaa. Sinulla on nykyhetken välimuisti, ja välimuistit valehtelevat.
Käytännön esimerkki: myynti arvonlisäveroineen
Konkreettinen voittaa abstraktin. Suomalainen yritys myy jotain sadalla eurolla plus 25,5 prosenttia arvonlisäveroa. Asiakas on velkaa 125,50. Siitä 100 on tuottoa, jonka pidät, ja 25,50 on arvonlisäveroa, jota pidät hallussasi tilittääksesi sen verottajalle. Siinä liikkuu kolme tiliä yhdessä tapahtumassa:
Päiväkirjavienti: lasku #1042, tapahtui 2026-05-20
debet Myyntisaamiset 12550 (vastaava kasvaa, meille velkaa)
kredit Myyntituotot 10000 (tulo kasvaa)
kredit Maksettava alv 2550 (velka kasvaa, olemme valtiolle velkaa)
Debetit: 12550. Kreditit: 10000 + 2550 = 12550. Se täsmää, joten se on pätevä vienti. Huomaa, ettei arvonlisäveroa ole tungettu tuottolukuun, vaan se istuu omalla velkatilillään ensimmäisestä hetkestä lähtien. Kun asiakas maksaa, se on toinen vienti: debet Pankkitili 12550, kredit Myyntisaamiset 12550. Saaminen menee nollaan, raha kasvaa, tuotto pysyy koskemattomana, koska se oli jo ansaittu laskutushetkellä.
Tästä syystä kahdenkertainen kirjanpito on ylimääräisen taulun arvoinen. Yhden sarakkeen malli niputtaa 12550:n yhteen rahaämpäriin, ja sitten saat tehdä aritmetiikkaa loputtomiin vastataksesi kysymykseen "paljonko olen alv:tä velkaa" tai "paljonko asiakkaat ovat jättäneet maksamatta". Täällä ne ovat pelkkiä tilien saldoja, aina oikein, koska invariantti takaa sen.
Kompromissit, rehellisesti
Kahdenkertainen kirjanpito ei ole ilmaista, ja vaikenemalla valehtelisin, jos teeskentelisin muuta.
- Enemmän kirjoituksia tapahtumaa kohti. Yksi myynti on kolme riviä tai useampi, ei yksi. Tallennustila on halpaa, joten tämä ei ole ongelma millään järkevällä mittakaavalla, mutta totta se on.
- Saldot ovat johdettuja, joten lukeminen maksaa enemmän. Miljoonan viennin summaaminen joka kerta kun dashboard latautuu on hidasta. Korjaus on saldon tilannevedos per tili per kausi: tarkistuspiste, josta aloitat, niin toistat vain tuoreet viennit. Voit silti toistaa nollasta varmuuden vuoksi, et vain tee sitä kuumalla polulla. Tilannevedos on välimuisti, jonka rakennat uudelleen päiväkirjasta, ei toinen totuuden lähde, jota mutatoit.
Ja vikamuodot, joita kannattaa varoa, ne joihin olen oikeasti törmännyt tai ollut törmäämäisilläni:
- Tasapainotarkistuksen unohtaminen kirjoituksessa. Jos mikään ei pakota debettejä täsmäämään kreditteihin transaktion sisällä, epätasapainoinen vienti livahtaa sisään ja korruptoi jokaisen sen alapuolisen raportin. Tee siitä rajoite, ei tapa.
- Valuuttojen sekoittaminen yhteen vientiin sitä sanomatta. Debet euroissa ja kredit dollareissa eivät "täsmää" pelkästään siksi, että luvut ovat samat. Monivaluuttaisuus vaatii eksplisiittisen valuuttakurssiviennin, tai sitten pidät yhden viennin yhdessä valuutassa. Päätä se ajoissa.
- Tilannevedoksen kohteleminen totuutena. Sinä päivänä kun joku päivittää tilannevedosta suoraan sen sijaan että rakentaisi sen uudelleen vienneistä, olet takaisin yhden sarakkeen helvetissä, ylimääräisillä seremonioilla höystettynä.
Miksi en koskaan palaisi takaisin
Syy, miksi aion jatkossakin käyttää kahdenkertaista kirjanpitoa kaikessa mikä koskettaa rahaa, ei ole perinne eikä compliance, vaikka ne tulevatkin kaupan päälle. Syy on se, että tietomalli kieltäytyy valehtelemasta. Invariantti tekee työn, johon tuhat huolellista koodikatselmointia voi vain toivoa pystyvänsä. Raha ei voi kadota, koska silloin kirjanpito ei täsmäisi ja kirjoitus epäonnistuisi. Historiaa ei voi hiljaa kirjoittaa uusiksi, koska UPDATEa ei ole. Mikä tahansa menneisyyden saldo on toistettavissa, koska päiväkirja on koko totuus ja pääkirja vain tapa katsoa sitä.
Voit rakentaa rahajärjestelmän ilman tätä. Moni on rakentanutkin, ja moni heistä on viettänyt viikonlopun täsmäyttäen saldoa, jonka ei olisi koskaan pitänyt liukua. Sulkakynäkauppiaat keksivät rakenteen, joka tekee siitä liukumasta mahdottoman. Vähintä mitä me voimme tehdä, transaktiot ja rajoitteet käytettävissämme, on käyttää sitä.
Haluatko keskustella tästä aiheesta? Kirjoita suoraan.
jami@impactnode.fi