<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: vvmaciel</title>
    <description>The latest articles on DEV Community by vvmaciel (@vvmaciel).</description>
    <link>https://dev.to/vvmaciel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F146315%2Ff7539f7a-a179-45a6-960c-b8eee6c8b2be.jpg</url>
      <title>DEV Community: vvmaciel</title>
      <link>https://dev.to/vvmaciel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vvmaciel"/>
    <language>en</language>
    <item>
      <title>Why a Double-Entry Ledger Is an Interesting Systems Programming Problem</title>
      <dc:creator>vvmaciel</dc:creator>
      <pubDate>Sat, 07 Nov 2026 02:41:51 +0000</pubDate>
      <link>https://dev.to/vvmaciel/why-a-double-entry-ledger-is-an-interesting-systems-programming-problem-1p8n</link>
      <guid>https://dev.to/vvmaciel/why-a-double-entry-ledger-is-an-interesting-systems-programming-problem-1p8n</guid>
      <description>&lt;p&gt;Accounting might seem like an unusual subject for a systems programming project. But implementing a double-entry ledger turns out to be a useful exercise in data modeling, transactional storage, and enforcing invariants.&lt;/p&gt;

&lt;p&gt;I've been exploring these ideas while developing &lt;code&gt;libledger&lt;/code&gt;, a small accounting library written in C and backed by SQLite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The central invariant
&lt;/h2&gt;

&lt;p&gt;In double-entry bookkeeping, a journal entry contains two or more postings, and the total debits must equal the total credits.&lt;/p&gt;

&lt;p&gt;For example, when a customer pays an invoice for $100:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Account&lt;/th&gt;
&lt;th&gt;Debit&lt;/th&gt;
&lt;th&gt;Credit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accounts Receivable&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$100&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important part is not the accounting terminology. It is the invariant that every valid entry must preserve.&lt;/p&gt;

&lt;p&gt;This gives us a concrete problem for software design: how do we ensure that invalid transactions can never become part of the committed accounting history?&lt;/p&gt;

&lt;h2&gt;
  
  
  Money is not a floating-point number
&lt;/h2&gt;

&lt;p&gt;An accounting system needs exact representations of monetary values.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;libledger&lt;/code&gt;, amounts are represented as 64-bit integers in the smallest unit of their currency, with overflow checks. This avoids introducing binary floating-point rounding errors into accounting calculations.&lt;/p&gt;

&lt;p&gt;Multi-currency accounting adds another important constraint: entries must balance independently for each currency.&lt;/p&gt;

&lt;p&gt;Currency conversion is a separate concern from the original transaction history.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLite as a transactional boundary
&lt;/h2&gt;

&lt;p&gt;A journal entry may contain multiple postings, but those postings represent one logical operation.&lt;/p&gt;

&lt;p&gt;We cannot safely write half an entry and hope to repair it later.&lt;/p&gt;

&lt;p&gt;In this implementation, validation precedes persistence, and SQLite transactions provide atomicity. The use of &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt; establishes a transactional boundary for updating the relevant accounting records.&lt;/p&gt;

&lt;p&gt;The database should contain either the complete valid operation or none of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking more than the write path
&lt;/h2&gt;

&lt;p&gt;Validation during normal operations is necessary, but not sufficient.&lt;/p&gt;

&lt;p&gt;Database files may also be modified outside the application, including through manual SQL operations.&lt;/p&gt;

&lt;p&gt;For that reason, &lt;code&gt;libledger&lt;/code&gt; includes consistency checks intended to detect discrepancies between accounting history and stored balances.&lt;/p&gt;

&lt;p&gt;This distinction matters: preventing inconsistent writes and detecting existing inconsistencies are separate responsibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why C?
&lt;/h2&gt;

&lt;p&gt;C makes many implementation choices explicit: integer ranges, memory ownership, error handling, and the relationship between the library and its persistence layer.&lt;/p&gt;

&lt;p&gt;For this particular project, I wanted a small implementation whose behavior could be examined directly.&lt;/p&gt;

&lt;p&gt;The library is accompanied by a command-line interface, which also makes it possible to test accounting behavior independently of a graphical interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  A literate implementation
&lt;/h2&gt;

&lt;p&gt;One additional experiment is the development process itself.&lt;/p&gt;

&lt;p&gt;The source code is extracted from a literate programming manuscript rather than maintained separately from its explanation.&lt;/p&gt;

&lt;p&gt;The build also executes selected shell sessions from the manuscript and verifies that their outputs match the documented results.&lt;/p&gt;

&lt;p&gt;This is an attempt to reduce the gap between what the documentation says and what the software actually does.&lt;/p&gt;

&lt;p&gt;You can explore the full source code and follow the project on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/libledger" rel="noopener noreferrer"&gt;
        libledger
      &lt;/a&gt; / &lt;a href="https://github.com/libledger/libledger" rel="noopener noreferrer"&gt;
        libledger
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A double-entry accounting library in C and SQLite, built through literate programming. Includes a CLI, accounting consistency checks, multi-currency support, and MCP integration for AI assistants.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;libledger&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A small double-entry accounting library in C, backed by SQLite.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;libledger is the companion implementation to &lt;strong&gt;&lt;a href="https://leanpub.com/literateledger" rel="nofollow noopener noreferrer"&gt;Double-Entry Accounting: A Literate Programming Implementation&lt;/a&gt;&lt;/strong&gt;, a book about building an accounting system from first principles using &lt;em&gt;literate programming&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The project includes a C library, a &lt;code&gt;ledger&lt;/code&gt; command-line application, and an MCP (Model Context Protocol) server that lets AI assistants interact with the books.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The book is the source.&lt;/strong&gt; The C implementation is extracted from the book's &lt;code&gt;noweb&lt;/code&gt; source rather than maintained separately from its explanation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Double-entry bookkeeping:&lt;/strong&gt; journal entries are checked for balanced debits and credits, separately for each currency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite storage:&lt;/strong&gt; multiple sets of books can be stored in a single database file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hierarchical chart of accounts:&lt;/strong&gt; accounts are organized using dotted codes; postings go to leaf accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact money representation:&lt;/strong&gt; amounts are stored as integers in each currency's smallest unit, not floating-point values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atomic posting:&lt;/strong&gt; entries are…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/libledger/libledger" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  Questions worth discussing
&lt;/h2&gt;

&lt;p&gt;Building an accounting engine raises some interesting engineering questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which invariants belong in application code, and which should also be enforced by the database?&lt;/li&gt;
&lt;li&gt;How should an application detect inconsistencies introduced by external database modifications?&lt;/li&gt;
&lt;li&gt;What is the most understandable way to represent exact monetary amounts across currencies?&lt;/li&gt;
&lt;li&gt;Can executable documentation make a small systems project easier to maintain?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions are interesting well beyond accounting software.&lt;/p&gt;

&lt;p&gt;What edge cases did I miss here? How do you approach data integrity and invariants in your systems projects? Let me know in the comments below!&lt;/p&gt;

</description>
      <category>c</category>
      <category>sqlite</category>
      <category>literate</category>
      <category>finance</category>
    </item>
  </channel>
</rss>
