ledger

package module
v0.0.0-...-9e9e9bf Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2025 License: MIT Imports: 18 Imported by: 0

README

Ledger Service

Go Report Card

Simple bookkeeping service, it has multiple accounts, tracks their balances and allows to create transactions.

A transaction can be created with CreateTransaction. Transaction amount will be added to the account balance. Positive transactions are topups, negative transactions are purchases. A transaction can be instant (with settled = true) or pending (with settled = false). Pending transactions will be settled or canceled later with SettleTransaction and CancelTransaction respectively.

Account balance is represented with available_balance and settled_balance fields. Settled transaction affect both balances. Pending purchases are subtracted from the available balance only. Each negative transaction is checked against available balance and INSUFFICIED_FUNDS error will be returned if the balance goes negative after such a transaction. It is possible to topup negative balance to any amount.

Compared to other popular approaches to solve Ledger System Design interview questions this approach:

  • has realtime account balance (it is updated instantly after each transaction is processed)
  • allows for insufficient funds check (precisely and instantly)
  • allows for complex logic around available and settled balances, or around negative balances
  • has fewer moving parts (no streams, no async workers)
  • scales infinitely by the number of accounts

Diagram

Application cores

There is one application core:

  • AccountsCore in accounts.go. Sharded by account id.
    • CreateAccount
    • GetAccount
    • CreateTransaction
    • GetTransaction
    • CancelTransaction
    • SettleTransaction
    • ListTransactions

Take a look at tests (accounts_test.go).

Cluster config

Pregenerated cluster config cluster_config.json has:

  • 3 nodes
  • 16 shards of Accounts
  • 3 replicas of each

How to run

  1. Clone this repository.
git clone git@github.com:evrblk/monstera-example.git

cd ./monstera-example/ledger
  1. Make sure it builds:
go build -v ./...
  1. Start a cluster with 3 nodes and a gateway server:
go tool github.com/mattn/goreman start
  1. Create 100 accounts:
go run ./cmd/dev seed-accounts
  1. Pick any account id from the previous step output.

  2. Run test scenario 1 which creates a pending transaction and then settles it for the account id:

go run ./cmd/dev scenario-1 --account-id=9fff3bf7d1f9561d

How to explore

For example, you want to understand how CreateTransaction method works:

  • Start reading from server.go in LedgerServiceApiServer.CreateTransaction().
  • Trace it down to monstera.MonsteraClient calls.
  • Optional: You can jump further if you want to read Monstera sources.
  • Find AccountsCoreAdapter.Update() and find CreateTransaction there.
  • Trace it down to AccountsCore.CreateTransaction().
  • Understand how simple the code is and how it takes advantage of sequential application of updates.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidId = errors.New("invalid id")
)

Functions

func DecodeAccountId

func DecodeAccountId(s string) (uint64, error)

func DecodeTransactionId

func DecodeTransactionId(s string) (*corepb.TransactionId, error)

func EncodeAccountId

func EncodeAccountId(id uint64) string

func EncodeTransactionId

func EncodeTransactionId(id *corepb.TransactionId) string

Types

type AccountsCore

type AccountsCore struct {
	// contains filtered or unexported fields
}

func NewAccountsCore

func NewAccountsCore(badgerStore *monstera.BadgerStore, shardLowerBound []byte, shardUpperBound []byte) *AccountsCore

func (*AccountsCore) CancelTransaction

func (*AccountsCore) Close

func (c *AccountsCore) Close()

func (*AccountsCore) CreateAccount

func (*AccountsCore) CreateTransaction

func (*AccountsCore) GetAccount

func (*AccountsCore) GetTransaction

func (*AccountsCore) ListTransactions

func (*AccountsCore) Restore

func (c *AccountsCore) Restore(reader io.ReadCloser) error

func (*AccountsCore) SettleTransaction

func (*AccountsCore) Snapshot

type AccountsCoreAdapter

type AccountsCoreAdapter struct {
	// contains filtered or unexported fields
}

func NewAccountsCoreAdapter

func NewAccountsCoreAdapter(accountsCore AccountsCoreApi) *AccountsCoreAdapter

func (*AccountsCoreAdapter) Close

func (a *AccountsCoreAdapter) Close()

func (*AccountsCoreAdapter) Read

func (a *AccountsCoreAdapter) Read(request []byte) []byte

func (*AccountsCoreAdapter) Restore

func (a *AccountsCoreAdapter) Restore(r io.ReadCloser) error

func (*AccountsCoreAdapter) Snapshot

func (*AccountsCoreAdapter) Update

func (a *AccountsCoreAdapter) Update(request []byte) []byte

type LedgerServiceApiServer

type LedgerServiceApiServer struct {
	gatewaypb.UnimplementedLedgerServiceApiServer
	// contains filtered or unexported fields
}

func NewLedgerServiceApiServer

func NewLedgerServiceApiServer(coreApiClient LedgerServiceCoreApi) *LedgerServiceApiServer

func (*LedgerServiceApiServer) CancelTransaction

func (*LedgerServiceApiServer) Close

func (s *LedgerServiceApiServer) Close()

func (*LedgerServiceApiServer) CreateAccount

func (*LedgerServiceApiServer) CreateTransaction

func (*LedgerServiceApiServer) GetAccount

func (*LedgerServiceApiServer) GetTransaction

func (*LedgerServiceApiServer) SettleTransaction

type LedgerServiceCoreApiMonsteraStub

type LedgerServiceCoreApiMonsteraStub struct {
	// contains filtered or unexported fields
}

func NewLedgerServiceCoreApiMonsteraStub

func NewLedgerServiceCoreApiMonsteraStub(monsteraClient *monstera.MonsteraClient, shardKeyCalculator LedgerServiceMonsteraShardKeyCalculator) *LedgerServiceCoreApiMonsteraStub

func (*LedgerServiceCoreApiMonsteraStub) CancelTransaction

func (*LedgerServiceCoreApiMonsteraStub) CreateAccount

func (*LedgerServiceCoreApiMonsteraStub) CreateTransaction

func (*LedgerServiceCoreApiMonsteraStub) GetAccount

func (*LedgerServiceCoreApiMonsteraStub) GetTransaction

func (*LedgerServiceCoreApiMonsteraStub) ListTransactions

func (*LedgerServiceCoreApiMonsteraStub) SettleTransaction

type LedgerServiceCoreApiStandaloneStub

type LedgerServiceCoreApiStandaloneStub struct {
	// contains filtered or unexported fields
}

func NewLedgerServiceCoreApiStandaloneStub

func NewLedgerServiceCoreApiStandaloneStub(accountsCore AccountsCoreApi) *LedgerServiceCoreApiStandaloneStub

func (*LedgerServiceCoreApiStandaloneStub) CancelTransaction

func (*LedgerServiceCoreApiStandaloneStub) CreateAccount

func (*LedgerServiceCoreApiStandaloneStub) CreateTransaction

func (*LedgerServiceCoreApiStandaloneStub) GetAccount

func (*LedgerServiceCoreApiStandaloneStub) GetTransaction

func (*LedgerServiceCoreApiStandaloneStub) ListTransactions

func (*LedgerServiceCoreApiStandaloneStub) SettleTransaction

type LedgerServiceMonsteraShardKeyCalculator

type LedgerServiceMonsteraShardKeyCalculator interface {
	ListTransactionsShardKey(request *corepb.ListTransactionsRequest) []byte
	GetTransactionShardKey(request *corepb.GetTransactionRequest) []byte
	GetAccountShardKey(request *corepb.GetAccountRequest) []byte
	CreateTransactionShardKey(request *corepb.CreateTransactionRequest) []byte
	CancelTransactionShardKey(request *corepb.CancelTransactionRequest) []byte
	SettleTransactionShardKey(request *corepb.SettleTransactionRequest) []byte
	CreateAccountShardKey(request *corepb.CreateAccountRequest) []byte
}

type ShardKeyCalculator

type ShardKeyCalculator struct{}

func (*ShardKeyCalculator) CancelTransactionShardKey

func (g *ShardKeyCalculator) CancelTransactionShardKey(request *corepb.CancelTransactionRequest) []byte

func (*ShardKeyCalculator) CreateAccountShardKey

func (g *ShardKeyCalculator) CreateAccountShardKey(request *corepb.CreateAccountRequest) []byte

func (*ShardKeyCalculator) CreateTransactionShardKey

func (g *ShardKeyCalculator) CreateTransactionShardKey(request *corepb.CreateTransactionRequest) []byte

func (*ShardKeyCalculator) GetAccountShardKey

func (g *ShardKeyCalculator) GetAccountShardKey(request *corepb.GetAccountRequest) []byte

func (*ShardKeyCalculator) GetTransactionShardKey

func (g *ShardKeyCalculator) GetTransactionShardKey(request *corepb.GetTransactionRequest) []byte

func (*ShardKeyCalculator) ListTransactionsShardKey

func (g *ShardKeyCalculator) ListTransactionsShardKey(request *corepb.ListTransactionsRequest) []byte

func (*ShardKeyCalculator) SettleTransactionShardKey

func (g *ShardKeyCalculator) SettleTransactionShardKey(request *corepb.SettleTransactionRequest) []byte

type UnimplementedLedgerServiceCoreApi

type UnimplementedLedgerServiceCoreApi struct{}

func (*UnimplementedLedgerServiceCoreApi) CancelTransaction

func (*UnimplementedLedgerServiceCoreApi) CreateAccount

func (*UnimplementedLedgerServiceCoreApi) CreateTransaction

func (*UnimplementedLedgerServiceCoreApi) GetAccount

func (*UnimplementedLedgerServiceCoreApi) GetTransaction

func (*UnimplementedLedgerServiceCoreApi) ListTransactions

func (*UnimplementedLedgerServiceCoreApi) SettleTransaction

Directories

Path Synopsis
cmd
dev command
gateway command
node command
standalone command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL