raiffeisen — ram-ui
- 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 — ram-ui — Case Study
Operator workspace embedded inside Raiffeisenbank Russia's RCRM. A React/Vite Module Federation host plus the supporting retail-account microservice — accounts, deposits, currency exchange, and exchange reports under one operator-facing surface.
Summary
ram-ui is the operator-facing workspace inside Raiffeisenbank Russia's retail CRM. The frontend is a Vite/React/TypeScript microfrontend host that runs both standalone and embedded in RCRM via single-spa, bundling several feature modules — main RAM views (accounts, deposits), currency exchange flows, and exchange-controller reports — into one shell. The supporting retail-account service is a Spring Boot 3 / Java 21 microservice on a hexagonal architecture, consuming account events from the bank's "New Interim" topic, owning a cached read model in PostgreSQL, and exposing a stable account API that replaces functionality previously carried by rmcp-product. 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
The retail operator workspace inside RCRM has to do several things at once: feel native inside RCRM, host multiple independent feature modules without cross-coupling them, and read account data from a model that the bank is in the middle of migrating away from. Specifically, account data has historically lived inside rmcp-product — a multidomain monolith that other teams want to retire. The retail multidomain team's job is to extract account ownership into a dedicated service (retail-account) without breaking the operator workflows that depend on it.
For a single-engineer fullstack engagement, the constraint is doing the migration and the host-shell work in parallel, while keeping the operator surface stable for production.
Approach
The work splits naturally along the two repositories. The frontend gets the host-shell pattern; the backend gets the strangler-fig migration off rmcp-product.
- Module Federation host with single-spa.
ram-uiis itself a microfrontend (single-spa-react+single-spa-css) so it mounts into RCRM at thercrm-microfrontend-coreui-depositsroot. It also runs standalone for local development. Inside it, three feature modules ship as Module Federation entries —coreui-deposits.js(the main RAM bundle),CurrencyExchangeMFApp.js, andExchangeReportMFApp.js— so the operator workspace can grow new modules without touching the existing ones. - Redux Toolkit + Redux Saga, plus signals where it makes sense. Operator workflows have enough cross-screen state and async coordination (refresh after MQ-driven events, optimistic state on account renames, etc.) to justify Saga.
@preact/signals-reactcovers fine-grained reactivity in places where Redux would be ceremony. - Hexagonal architecture for
retail-account. Three Gradle modules (retail-accountservice,retail-account-apifor public DTOs,retail-account-clientFeign wrapper for downstream consumers), plus a tinytranslitutility module. Public contracts live inretail-account-apiso other services depend on stable types without pulling in service internals. - Event-sourced read model. The service subscribes to "New Interim" account events (Open, Close, Not Closed, Chg_CBA, Chg_CrIntType/SubType) over Spring Boot Artemis JMS, projects them into the
bmw.accounttable in the RO PostgreSQL database, and serves reads from there. DB2 (WebDB / RAMS) participates for historical lookups during the migration. - Caching as a first-class concern. Per-owner, per-id, and per-CBA account caches; reference-data caches for currencies, branches, BIC, correspondents. Each cache invalidates on its specific event class plus a TTL fallback. Configuration lives in
application.yml. - Resilience4j and circuit breakers. Downstream calls (notably to
rmcp-productwhile it still owns some data) go through circuit breakers so a slow upstream doesn't hold operator screens hostage. - OpenAPI as the boundary.
retail-account-apipublishes the OpenAPI spec; downstream consumers (includingcore-ui-bffandretail-insurances) depend on the typedretail-account-apiartifact rather than ad-hoc HTTP calls.
Solution
Two repositories, one product. The frontend is a Vite/React/TypeScript app with two Vite configs: vite.config.ts for standalone host mode (StandaloneLayout) and vite.config.mf.ts for the Module Federation build that ships coreui-deposits.js, CurrencyExchangeMFApp.js, and ExchangeReportMFApp.js as MF entries. The single-spa lifecycles wrap a MicroFrontendLayout that takes RCRM-supplied props and renders the appropriate feature module. Internal routing is react-router 7. Forms are formik + yup; cross-feature state is Redux Toolkit with Saga middleware; auth is react-oidc-context.
The backend is a layered Spring Boot 3.5 service on Java 21 with explicit Ports & Adapters: domain core in retail-account, public DTOs in retail-account-api, Feign client in retail-account-client. Spring Boot Artemis listeners consume the bank's account event topic; MapStruct maps inbound DTOs to JPA entities; Flyway manages the bmw.account schema in PostgreSQL. The service exposes a documented HTTP API (Springdoc), publishes Prometheus metrics, and uses Resilience4j to wrap remaining downstream calls. JaCoCo + Allure track test coverage and reporting (Sonar project ru.raiffeisen.retail.multidomain:retail-account).
Architecture
The frontend has three layers: the standalone shell (StandaloneLayout, App.tsx) for direct hosting; the microfrontend layer (MicroFrontendLayout + single-spa-react lifecycles in main.mf.tsx) for RCRM embedding; and the feature modules (RamUIApp, CurrencyExchangeMFApp, ExchangeReportMFApp) that ship as separate Module Federation entries from the same codebase. Auth tokens, theming, and operator context flow from RCRM into the workspace via single-spa props, then down through the host into the feature modules.
The backend reads write-side events from JMS, projects them into PostgreSQL, and serves reads from a cached layer. The hexagonal split means the JMS adapter, the JPA adapter, and the HTTP adapter are interchangeable from the domain core's perspective — the same business logic runs whether the trigger is an inbound HTTP call from core-ui-bff or an account-open event from "New Interim".
Key features shipped
- Module Federation host that bundles main RAM views, currency exchange, and exchange-controller report into one operator workspace
- Standalone + RCRM-embedded dual-build pattern
- Account read service with event-sourced projection from "New Interim" into
bmw.account - Per-owner / per-id / per-CBA account caches with event-driven invalidation
- Strangler-fig replacement of
rmcp-productaccount functionality - Public Feign client (
retail-account-client) for downstream consumers - OAuth2-secured HTTP API documented via Springdoc
Outcome
The operator workspace runs in production embedded in RCRM, and the retail-account service has taken over account responsibilities from rmcp-product for the modules in scope. The single-engineer fullstack engagement has held — the discipline of sharing types across services via the published retail-account-api artifact has kept the migration safe.
- Live in production inside RCRM, with three Module Federation feature modules under one host
retail-accountserving account reads with cached projection from the bank's account event stream- One embedded topsweteam engineer carrying the workstream across two repositories
Tech stack
Frontend: React 18, TypeScript, Vite (dual standalone + MF builds), @fcc/ui v12.x, @fcc/icons, Redux Toolkit, Redux Saga, @preact/signals-react, react-router 7, formik, yup, styled-components, date-fns, lodash
Frontend microfrontend: single-spa-react, single-spa-css
Frontend auth: oidc-client-ts, react-oidc-context
Backend: Java 21, Spring Boot 3.5, Gradle 8 multi-module, Spring Data JPA, Spring Boot Artemis, Spring Cloud OpenFeign, Spring Web Services
Backend architecture: Hexagonal (Ports & Adapters), MapStruct, Lombok, Resilience4j circuit breaker
Backend data: PostgreSQL (RO database), DB2 (WebDB / RAMS), Flyway, ShedLock for scheduled jobs
Backend integrations: Spring Boot Artemis JMS for "New Interim" account events, Feign clients for downstream services
Backend ops: Springdoc OpenAPI, Micrometer + Prometheus, JaCoCo, Allure, Sonar
Deploy: Helm, Kubernetes (prod-teleport-ha-retail-* clusters), Raiffeisen Artifactory
What we learned
- A microfrontend host is the right vehicle when the operator workspace grows by feature module rather than by screen. Bundling currency exchange, exchange reports, and main RAM under one Module Federation host means each module ships independently while the operator sees one workspace.
- Event-sourced projection beats keeping a foreign data model live during a migration. Reading account state from the bank's event stream lets us own a clean read model in
bmw.accountwithout forcing the bank to re-plumb the upstream system. The cache shape (owner / id / CBA) was driven by what the operator screens actually query, not by what the upstream emits. - Publishing a stable typed contract artifact (
retail-account-api) is what makes the strangler safe. Downstream services depend onretail-account-apiinstead of HTTP-calling the legacyrmcp-product, and version bumps surface as compile errors rather than runtime regressions.
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