Avatar
← Back to Portfolio

Midas Core: Balances API

Spring Boot microservice that ingests transaction events from Kafka and serves real-time balances on a lightweight REST endpoint. One deployable handles streaming updates and read requests on a fixed port for operational simplicity.

JavaSpring BootSpring WebSpring Data JPAApache KafkaPostgreSQLMavenJUnit
Midas Core architecture diagram showing Kafka ingestion, Spring components, and the balances API.
Kafka events feed the balances table; the REST controller reads from the same Spring context.

The Problem

Users needed a trustworthy way to check balances inside Midas, but the core service only ingested transactions. Spinning up a dedicated read surface would add another deployable and operational overhead for what amounts to a single endpoint.

The Solution

Extend Midas Core to answer read requests alongside its Kafka consumer. A new Spring MVC controller exposes GET /balance and reuses the same transactional model used by the consumer.

  • No new service to provision; one build, one deployment.
  • Fast response path: single SQL lookup via Spring Data JPA.
  • Graceful defaults: unknown users return a zeroed balance DTO.
  • Future-proof: if reads grow, the controller layer can be extracted.

Architecture & Technical Details

System Flow

  • Incentive service emits transaction events to Kafka.
  • Midas Core consumes each event, applying mutations to a balances table in PostgreSQL.
  • Clients issue GET /balance?userId=... on port 33400.
  • The controller returns a serialized Balance DTO with the latest amount.

Runtime View

  • Kafka listener runs continuously, applying each transaction to the aggregate balance.
  • REST controller lives inside the same Spring context, reading via a JPA repository.
  • All traffic is served on a fixed port for simple routing and security rules.

Endpoint Contract

  • Path: /balance
  • Method: GET
  • Query: userId
  • Response: JSON Balance object
  • Fallback: Unknown user → amount 0
  • Port: 33400

Persistence Model

The balances table is keyed by user ID with a decimal amount column. Kafka-driven writes are the source of truth, while reads stay pure by issuing a single repository call per request.

Why This Cut Works

Architecture Tradeoff

A separate read service would be clean but slower to ship and support. Folding the endpoint into Core keeps the surface area minimal while retaining a clear path to extract later.

Event-Driven Correctness

The database remains the canonical source—Kafka listeners derive balances deterministically, so the REST layer stays stateless and safe.

Operational Simplicity

One build, one deployment target, one health check. Less to monitor for an intentionally small feature.

Key Features

  • Single call to fetch a user balance with millisecond latency.
  • Zero default for unknown users to avoid leaking errors to clients.
  • JSON contract that mirrors the shared Balance DTO.
  • Kafka-driven transaction ingestion and reconciliation.
  • Runs alongside the Kafka listener inside one Spring Boot application.
  • Fixed port 33400 for predictable routing and firewall rules.

Challenges & Learnings

Architecture Tradeoff

Balancing separation of concerns with delivery speed led to layering a read controller inside Core. The code stays modular so the controller can be extracted once traffic justifies it.

Event-Driven Correctness

Keeping the database as the source of truth avoids drift between streaming ingest and reads. The controller never mutates data, it only projects the latest balance.

Operational Simplicity

Fewer deployables mean less monitoring, fewer secrets, and simpler on-call playbooks—ideal for a feature that started as a small experiment.

What I Built

  • Spring Boot application with a Kafka listener that applies transactions to the balances table.
  • REST controller that returns the Balance DTO on /balance.
  • Configuration for serving on port 33400 and graceful zero defaults for unknown users.
  • JUnit coverage around the controller and repository interactions.