raiffeisen — commissions
- client
- Raiffeisenbank Russia
- industry
- Banking / Financial services
- role
- Dedicated embedded fullstack engineer
- timeline
- May 2025 – present, ongoing
- team
- 1 fullstack engineer
- status
- ongoing · ongoing
Raiffeisen — commissions — Case Study
Commissions management surface for Raiffeisenbank Russia's retail saving-products. React + Vite frontend paired with a Kotlin/Spring Boot service on Java 21, deployed via Helm to the bank's Kubernetes cluster.
Summary
commissions is the commissions workflow for Raiffeisenbank Russia's retail saving-products — the screens bank operators use to define and review commission rules, generate Excel reports, and the Kotlin service that owns the data, the IBM MQ integrations, and the OpenAPI contract. The frontend (commissions-ui, internal name rcsm-ui) and backend (rams-commission) share a single OpenAPI spec and regenerate types from it on both sides. topsweteam's embedded engineer is the active fullstack contributor.
- Client: Raiffeisenbank Russia
- Engagement: 1 dedicated embedded fullstack engineer (topsweteam)
- Status: Live in production, ongoing
- Industry: Banking / Financial services
Challenge
Commissions for retail saving-products touch a slow-moving compliance domain: every rule change has to be auditable, every report has to match what other downstream systems already report, and the integrations are with the bank's existing enterprise message bus rather than something modern. The product needs an operator-friendly UI that doesn't drift from the backend's data model, and a backend that talks the same protocols the bank's older systems expect — IBM MQ rather than Kafka, Excel rather than CSV, an OpenAPI contract rather than ad-hoc JSON.
For a one-engineer fullstack engagement, the constraint is keeping the two halves in lockstep without separate frontend and backend specialists negotiating a contract over Slack.
Approach
The engagement is fullstack-by-design. Same engineer owns both repositories, both deployments, both code reviews. The discipline that keeps this safe is the OpenAPI contract.
- OpenAPI as the single source of truth.
rams-commissionownsopenapi_spec.yaml. The backend regenerates server stubs from it via the Gradleopenapi.generatorplugin. The frontend regenerates a TypeScript Fetch client viaopenapi-generator-cli(npm run generate_api). Schema drift is impossible — both sides recompile against the same spec. - Kotlin + Spring Boot for the service. Java 21 toolchain, Spring Boot starters for web, JPA, validation, security, OAuth2 resource server. The bank's
Raiffeisen Artifactoryis the dependency mirror; mavenCentral is unreachable from the build.libs.versions.tomlgives us deterministic dependency resolution. - PostgreSQL with Liquibase for migrations.
preliquibasefor environment-specific bootstrap, then ordinary Liquibase changelogs for schema evolution. Both run on app start so a fresh environment is always onedocker compose upaway. - IBM MQ for messaging. The bank's enterprise messaging stack is IBM MQ (Jakarta JMS client +
spring-jms). We don't fight it — we integrate. - Apache POI for Excel exports. Operators want
.xlsx, not CSV. POI is the reliable, correct answer. - React 18 + Redux Toolkit + react-hook-form + Zod for the UI. Redux Toolkit because the operator workflows have enough cross-screen state to justify it; react-hook-form + Zod for the rule-editing forms (they're complex, with conditional validation).
@fcc/uifor everything visual. - OIDC for identity on both sides.
react-oidc-contexton the frontend; Spring Security OAuth2 Resource Server on the backend, validating tokens against the bank's IdP.
Solution
Two repositories, one product. The Kotlin service is a standard Spring Boot 3 application: REST controllers generated from the OpenAPI spec, JPA entities and repositories over PostgreSQL, Liquibase migrations, IBM MQ listeners and publishers for the messaging integrations, Apache POI for the Excel export endpoints, Springdoc serving the live API docs, Micrometer publishing Prometheus metrics. The frontend is a Vite/React/TypeScript app rendering against @fcc/ui, with Redux Toolkit slices for the operator state, react-hook-form + Zod for the editing forms, and a generated TypeScript Fetch client that strictly mirrors the backend.
Both halves deploy to the bank's Kubernetes cluster via Helm — the frontend has the same application + proxy chart pair the rest of the platform uses; the backend deploys as a standard Spring Boot service. CI is GitLab.
Architecture
A single OpenAPI spec lives in the backend repository. The backend's Gradle build runs the OpenAPI generator on every build to regenerate Spring controllers and DTOs. The frontend's npm run generate_api script regenerates a TypeScript Fetch client into src/API/rcsm. The frontend imports the generated client directly; UI code never hand-writes request bodies or response types. When the spec changes, both sides recompile and any drift surfaces as a TypeScript or Kotlin compile error.
The backend is a layered Spring Boot service: controllers (generated) → application services → JPA repositories → PostgreSQL. IBM MQ listeners run as Spring components subscribing to the bank's queues; the same configuration also publishes outbound messages. Excel exports are streaming POI workbooks served as application/vnd.openxmlformats-officedocument.spreadsheetml.sheet. Schema migrations run on startup via Liquibase.
Key features shipped
- Commissions rule configuration UI (with conditional validation via Zod)
- Excel export of commission reports (POI-backed, streaming)
- IBM MQ integrations for the bank's downstream systems
- OAuth2-secured REST API generated from a shared OpenAPI spec
- Operator workflows on
@fcc/ui, consistent with the rest of the bank's tooling
Outcome
The commissions product runs in production as part of Raiffeisenbank Russia's retail saving-products tooling. The fullstack-by-design engagement has held — the OpenAPI contract has eliminated the usual "frontend says X, backend says Y" friction that single-engineer projects normally hit when they touch both sides.
- Live in production for the saving-products operators
- Single OpenAPI spec, two codebases regenerating against it
- One embedded topsweteam engineer carrying both halves of the workstream
Tech stack
Frontend: React 18, TypeScript, Vite, @fcc/ui v12.x, @fcc/icons, Redux Toolkit, react-hook-form, Zod, react-router-dom, styled-components, axios
Frontend auth: oidc-client-ts, react-oidc-context
Backend: Kotlin (JVM 21), Spring Boot, Spring Data JPA, Spring Security OAuth2 Resource Server, Spring JMS
Backend data: PostgreSQL, Liquibase, preliquibase
Backend integrations: IBM MQ (Jakarta JMS client), Apache POI (xlsx export)
Backend ops: Springdoc OpenAPI, Micrometer + Prometheus
Contract: OpenAPI 3, codegen on both sides (openapi-generator-cli typescript-fetch and Gradle openapi.generator)
Build: Gradle (Kotlin DSL), npm, Vite
Deploy: Helm (application + proxy charts), Kubernetes, Raiffeisen Artifactory
What we learned
- A shared OpenAPI spec replaces the frontend/backend negotiation. When one engineer owns both halves, drift only happens when the contract isn't enforced. Generating both sides from the same spec is the cheapest way to enforce it.
- Use the bank's plumbing, don't try to modernize it from inside the product. IBM MQ and Apache POI are not glamorous, but every other system in the bank already speaks them. Picking Kafka or CSV here would have been someone else's integration problem to solve.
- Redux Toolkit is the right call when the operator workflow has cross-screen state. Form-local state (react-hook-form) and global state (RTK) compose cleanly; reaching for Redux from day one is the wrong default, but here the workflows justified it.
tech
- build
- Vite, Module Federation, Gradle (Kotlin DSL)
- frontend
- React 18, TypeScript, Redux Toolkit, Redux Saga, jotai, react-hook-form + Zod, formik + yup, styled-components, @fcc/ui (Raiffeisen design system)
- backend
- Kotlin, Java 21, Spring Boot 3.x, Spring Data JPA, Spring Cloud OpenFeign, Spring Web Services, Spring Boot Artemis (JMS), IBM MQ (Jakarta), Resilience4j
- data
- PostgreSQL, DB2, Liquibase, Flyway, Redis, Apache POI, PDFBox, ICU4J
- auth
- OIDC via react-oidc-context, OAuth2 Resource Server, OTP signing
- microfrontend
- single-spa-react, single-spa-css
- contract
- OpenAPI 3 codegen on both sides, wsdl2java for SOAP integrations, Springwolf for JMS
- ops
- Springdoc, Micrometer + Prometheus, OTel tracing, Sonar, Allure, JaCoCo, ShedLock
- deployment
- Helm, Kubernetes, Raiffeisen Artifactory, GitLab CI