Sprank is a robust, ledger-based banking and payment processing API built with Spring Boot.
This project was engineered to manage a complex, real-world financial domain model, using a double-entry bookkeeping system for transaction integrity and audibility. It accurately handles a wide array of core banking features, including:
- Cash deposits and transfers
- Real-time transaction processing
- Taxes and service fees calculations
- QR payments and payment requests
- Multiple currencies
Index
- The main purpose of the project
- Screenshots
- Features
- Project structure
- Technical highlights
- Limitations
My goal is to highlight my ability to create complex systems with complex business logic. That's what inspired many
decisions like the ledger-based system, of course having a balance column in the database would've been much easier
but not as interesting.
Query:
SELECT t.id, t.created_at, tl.id, tl.account_id, running_balance, debit_amount debit, credit_amount credit
FROM transaction t
JOIN sprank.transaction_leg tl on t.id = tl.transaction_id
ORDER BY t.created_at;The screenshot below showcases a few deposits and transactions (the ones that were used to demo the Grafana dashboard)
The main features implemented right now are:
- Double-entry bookkeeping system for transaction auditability and integrity
- Alias system for easy to remember accounts (similar to Argentina's alias/CVU/CBU system)
- Administrative freezing and unfreezing of accounts
- Multiple currencies
- Transaction requests, which allow for QR-code based payments
- Cash deposits with a cash reserve system
- Configurable taxes and fees that can apply depending on different facts about the transaction, accounts involved, customers, etc.
- Externally managed auth using Keycloak as IdP
- Fully Dockerized setup (application, DB and metrics)
- CI/CD GitHub pipeline for continuous builds and tests
- Business metrics with Micrometer/Prometheus/Grafana
The project follows a simple structure. It is not domain separated since it only has a few domains, and is implemented a bit like how you'd implement a single-domain microservice.
main
├── java/dev/maddock/sprank
│ ├── advice Controller advice (exception handling)
│ ├── annotation Utility annotations
│ │ ├── resolver Annotation resolvers (for Spring's MethodArgumentResolvers)
│ │ └── validator Validators (for annotations used to validate DTOs)
│ ├── config Config classes
│ │ ├── fee Fee-related transaction modifiers
│ │ └── tax Tax-related transaction modifiers
│ ├── controller HTTP Controllers (@RestController)
│ │ ├── account
│ │ ├── customer
│ │ ├── deposit
│ │ ├── request
│ │ └── transaction
│ ├── dto DTOs
│ │ ├── account
│ │ ├── customer
│ │ ├── deposit
│ │ ├── error
│ │ ├── request
│ │ └── transaction
│ ├── entity JPA entities
│ ├── enumeration Enums
│ ├── exception Business-specific exceptions (like InsufficientFundsException)
│ │ ├── account
│ │ ├── auth
│ │ ├── customer
│ │ ├── deposit
│ │ ├── exchange
│ │ └── transaction
│ ├── repository JPA repositories
│ └── service Service interfaces
│ └── impl Service implementations
└── resources
├── db Database stuff, includes a script with default accounts for transaction modifiers
│ └── migration Flyway migrations
└── META-INF
-
Stack: Spring Boot, MySQL (JPA/Hibernate)
-
Security: Managed by Keycloak as the dedicated identity provider (IdP), see docker-compose.yml
-
CI/CD: Fully containerized with a Dockerfile and docker-compose.yml setup. The pipeline includes a build and a test step and is available on the GitHub Actions page
-
Testing: The API is thoroughly tested and has reached 90% test coverage ( see test/). The current test count as of commit 62aa1dd is 129, covering over 6000 lines of code.
-
Documentation: The project is also extensively documented, with Javadoc comments, READMEs and even an OpenAPI Specification.
-
Monitoring: The project exposes Micrometer metrics over the
/actuator/prometheusendpoint, allowing monitoring of different business and runtime metrics (screenshot) -
Code quality: Apart from being extensively documented, the code applies common practices like SOLID and DRY.
There's a small limitation with the current system. Ideally, in a double-entry bookkeeping system, amounts are stored in a common currency, used as a reference value (commonly known as the "base" or "functional" currency). Being an argentinian myself, I'm well accustomed to inflation and volatility, and a conscious choice has been made to record transactions using each account's own currency. This is technically incorrect, but a decent and (in my opinion) acceptable compromise.
Using a single currency would require accounting for asset loss/growth thanks to currency value fluctuations, which would require handling income and expenses. This requirement also includes calculating said income and expenses, which in turn increases complexity considerably.
TL;DR: I deliberately chose to implement double-entry bookkeeping slightly incorrectly for simplicity and to reduce time to portfolio (TTP!)


