raiffeisen — retail-insurances
- 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 — retail-insurances — Case Study
Backend service for retail insurances inside Raiffeisenbank Russia. Kotlin / Spring Boot on Java 21, serving R-Online and RCRM, deployed via Helm to the bank's Kubernetes cluster.
Summary
retail-insurances is the backend that powers retail insurance display and management across two of Raiffeisenbank Russia's customer surfaces — R-Online (the retail-banking application) and RCRM (the operator CRM). The service owns the insurance data, the SOAP integrations to upstream insurance providers, and the OAuth2-protected REST API the frontends consume. It runs in the bank's three-tier Kubernetes environment (test, preview, prod) under namespaces insurance-test / insurance-preview / insurance-prod, with logs in the bank's CLMP/Kibana stack and metrics in Grafana. topsweteam's embedded engineer contributes to its development.
- Client: Raiffeisenbank Russia
- Engagement: topsweteam embedded engineer contribution
- Status: Live in production
- Industry: Banking / Insurance / Financial services
Challenge
Insurance, inside a bank, sits at the intersection of two reluctant integration worlds: the customer-facing surfaces want a clean REST API, but the upstream insurance providers still speak SOAP, and the bank's internal messaging is JMS-based. The service has to bridge those without leaking the upstream data shapes into the public API, and without losing the auditability the regulator expects (OTP-signed operations, traced calls, persisted document trails).
It also has to live within the bank's curated stack — there's a Raiffeisen-internal dependencies-plugin Gradle plugin that pins the allowed dependencies, the security stack (securityInternal, securityExternal), and the OTP signing module. New libraries don't get added casually.
Approach
The service is built as a standard Spring Boot 3 microservice on Java 21, but with a discipline imposed by the bank's curated stack and the SOAP-to-REST adaptation it has to perform.
- Use the bank's curated dependency plugin.
ru.raiffeisen.boot.plugin.dependencies-pluginprovides the approved set of internal starters —securityInternal,securityExternal,otpSign,statement,featureToggles. We work within it rather than adding ad-hoc dependencies. - SOAP at the edge, REST at the surface. The bank's upstream insurance providers are integrated via Spring Web Services with
wsdl2java-generated client stubs (Apache HttpClient 5 underneath). The service translates them into a clean REST API documented via Springdoc. - Redis cache for hot reads. Insurance product/catalog data is cached in Redis through Spring Cache; coroutines (
kotlinx-coroutines) handle the inevitable I/O-fan-out without thread overhead. - PostgreSQL for owned state, Liquibase for migrations.
preliquibaseruns first to bootstrap database/schema artifacts, then ordinary Liquibase changelogs evolve the schema. - JMS for the bank's messaging. Spring Boot Artemis is the bank's chosen JMS broker;
pooled-jmsfor connection pooling. Inbound and outbound contracts are documented via Springwolf so JMS isn't a black box to the rest of the platform. - OAuth2 + OTP signing for sensitive operations.
securityInternalfor service-to-service calls,securityExternalfor end-customer-originating requests,otpSignfor high-stakes operations the regulator expects to be cryptographically signed. - Tracing and metrics by default. Micrometer with OTel bridge, Prometheus registry, Resilience4j wrapping the SOAP calls, ShedLock keeping scheduled jobs single-runner across the cluster.
- Document generation in-process. PDFBox for PDF generation, Apache POI for spreadsheet exports, ICU4J for locale-aware formatting (currency, dates, Russian morphology).
- Multi-environment deployment. Three Kubernetes clusters (
prod-teleport-ha-retail-test,prod-teleport-ha-retail-preview,prod-teleport-ha-retail-prod) with environment-scoped namespaces and ingresses; configuration in the bank'sinfra-confAnsible repo.
Solution
A single Spring Boot 3 service, Kotlin codebase, deployed identically across three environments. The service exposes its REST API at /api/retail-insurances/api/v1/* (proxied through the bank's external API gateway at *.online.raiffeisen.ru) and a Swagger UI at /swagger-ui/index.html. Inbound calls authenticate via the bank's OAuth2 setup; high-stakes calls additionally validate an OTP signature.
Outbound integrations split three ways: SOAP to the upstream insurance providers (via wsdl2java-generated stubs), REST to internal bank services (via retail-account-api Feign clients and similar typed contracts), and JMS to the bank's message bus (via Spring Boot Artemis). The service owns its PostgreSQL schema via Liquibase; reference and product data is cached in Redis with TTL invalidation. PDFBox generates customer-facing documents in-process; ICU4J handles formatting where Russian-language localization (date forms, currency rendering) matters.
Architecture
The service follows a standard Spring Boot layout — controllers, application services, domain code, JPA repositories — with explicit adapters for each integration boundary: a SOAP adapter package wrapping the wsdl2java-generated stubs, a JMS adapter package for inbound and outbound messaging, an HTTP adapter package for downstream REST calls. The shape lets each integration evolve independently of the public API.
Observability is uniform across the three environments. Logs go to the CLMP/Kibana stack (filter kubernetes.container_name: retail-insurances); metrics go to Prometheus and surface in the bank's Grafana k8s-services dashboard; traces propagate via OpenTelemetry through Micrometer's OTel bridge. JMS contracts are documented through Springwolf so other teams discover the message shapes without reading code.
Key features shipped
- REST API for retail insurance display and management, consumed by R-Online and RCRM
- SOAP integrations to upstream insurance providers via
wsdl2java-generated clients - OAuth2-protected endpoints with OTP-signed mutations
- Redis-backed caching layer for product/catalog data
- JMS integration with the bank's Spring Boot Artemis broker, documented via Springwolf
- PDF generation (PDFBox) and locale-aware formatting (ICU4J) for customer documents
- Three-environment deployment (test / preview / prod) on the bank's Kubernetes platform
Outcome
The service runs in production, serving insurance flows across both R-Online and RCRM. Logs and metrics are visible in the bank's standard observability stack across all three environments; the SOAP adapters are isolated enough that upstream changes don't ripple into the public API.
- Live in production
- API consumed by both R-Online (customer) and RCRM (operator) surfaces
- topsweteam engineer contributing inside the bank's retail multidomain team
Tech stack
Runtime: Java 21, Kotlin, Spring Boot 3.x via Raiffeisen's dependencies-plugin
Web: Spring Web, Spring Cloud OpenFeign, Spring Web Services + wsdl2java, Apache HttpClient 5
Data: PostgreSQL, Liquibase, preliquibase, Spring Data JPA
Cache: Spring Cache, Redis
Messaging: Spring Boot Artemis (JMS), pooled-jms, Springwolf
Async: Kotlin Coroutines, ShedLock
Auth: Raiffeisen securityInternal + securityExternal starters, otpSign, Spring Security OAuth2 client + resource server
Ops: Springdoc OpenAPI, Micrometer + OTel tracing bridge, Prometheus
Documents: PDFBox, Apache POI, ICU4J
Build: Gradle (Kotlin DSL), Raiffeisen Artifactory
Deploy: Helm, Kubernetes (test / preview / prod clusters), GitLab CI
What we learned
- Curated dependency plugins are a feature, not a friction. The bank's
dependencies-pluginlooks restrictive at first, but it removes a category of recurring decisions (which Spring Security version, which Boot version, which OAuth2 client setup) and surfaces compliance requirements as a stack property rather than a per-service argument. - SOAP-to-REST translation belongs at the edge, not in the domain. Keeping the
wsdl2java-generated clients confined to a single adapter package means the public REST API never has to speak XML namespaces orxsd:dateTime, and provider-side schema changes stop at the adapter. - Springwolf for JMS pays off the first time another team needs to integrate. Documenting message shapes lives or dies on whether the docs are generated from code; Springwolf does for JMS what Springdoc does for HTTP, and the cross-team conversations get shorter.
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