← back to clients
banking · ongoing

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-ui is itself a microfrontend (single-spa-react + single-spa-css) so it mounts into RCRM at the rcrm-microfrontend-coreui-deposits root. 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, and ExchangeReportMFApp.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-react covers fine-grained reactivity in places where Redux would be ceremony.
  • Hexagonal architecture for retail-account. Three Gradle modules (retail-account service, retail-account-api for public DTOs, retail-account-client Feign wrapper for downstream consumers), plus a tiny translit utility module. Public contracts live in retail-account-api so 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.account table 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-product while 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-api publishes the OpenAPI spec; downstream consumers (including core-ui-bff and retail-insurances) depend on the typed retail-account-api artifact 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-product account 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-account serving 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.account without 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 on retail-account-api instead of HTTP-calling the legacy rmcp-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
type any key to open chatopen chat