NV
NordVarg
ServicesTechnologiesIndustriesCase StudiesBlogAboutContact
Get Started

Footer

NV
NordVarg

Software Development & Consulting

GitHubLinkedInTwitter

Services

  • Product Development
  • Quantitative Finance
  • Financial Systems
  • ML & AI

Technologies

  • C++
  • Python
  • Rust
  • OCaml
  • TypeScript
  • React

Company

  • About
  • Case Studies
  • Blog
  • Contact

© 2025 NordVarg. All rights reserved.

Regional Bank
•Banking•
March 2024
•

Core Banking System Modernization

Challenge

Legacy COBOL system unable to support digital banking initiatives

Solution

Modern core banking platform built with OCaml for type safety and reliability

Key Results

  • ✓100% uptime during migration from legacy system
  • ✓Transaction processing speed increased by 400%
  • ✓Enabled real-time payments and mobile banking
  • ✓$30M annual cost savings from infrastructure optimization

Technologies Used

OCamlPostgreSQLKubernetesgRPCReactTypeScript
11 min read
Share:

Executive Summary#

A regional bank with 50+ years of history was constrained by a COBOL-based core banking system built in the 1980s. Unable to compete with digital-first banks and fintechs, they needed a complete modernization while ensuring zero downtime and maintaining regulatory compliance.

NordVarg designed and implemented a modern core banking platform using OCaml, chosen for its strong type system, reliability, and proven track record in financial systems (used by Jane Street, Bloomberg, and Facebook).

The Challenge#

Legacy System Limitations#

The 40-year-old COBOL system presented critical challenges:

  • No API support: Impossible to build modern digital experiences
  • Batch processing: End-of-day batch jobs taking 6+ hours
  • Mainframe dependency: Annual costs of $10M for hardware/licensing
  • Skill shortage: Only 3 engineers who understood the codebase
  • Rigidity: Simple changes requiring months of development

Business Pressures#

  • Losing customers to digital-first competitors
  • Unable to launch mobile banking
  • Missing out on real-time payment opportunities
  • High operational costs
  • Regulatory pressure to improve resilience

Technical Requirements#

  • Zero downtime migration: Cannot disrupt 24/7 banking operations
  • Data integrity: Absolute correctness in financial calculations
  • Regulatory compliance: SOC 2, PCI-DSS, banking regulations
  • Performance: Handle 10,000 transactions/second
  • Auditability: Complete transaction history and traceability

Why OCaml?#

We selected OCaml for the core banking logic due to its unique strengths:

1. Type Safety#

ocaml
1(* Money type that prevents arithmetic errors *)
2module Money : sig
3  type t
4  val zero : t
5  val of_string : string -> (t, string) result
6  val add : t -> t -> t
7  val subtract : t -> t -> (t, string) result
8  val multiply : t -> float -> t
9  val compare : t -> t -> int
10  val to_string : t -> string
11end = struct
12  (* Internal representation: cents to avoid floating point *)
13  type t = int64
14  
15  let zero = 0L
16  
17  let of_string s =
18    match String.split_on_char '.' s with
19    | [dollars; cents] ->
20        (try
21          let d = Int64.of_string dollars in
22          let c = Int64.of_string cents in
23          Ok (Int64.add (Int64.mul d 100L) c)
24        with Failure _ -> Error "Invalid money format")
25    | _ -> Error "Invalid money format"
26  
27  let add = Int64.add
28  
29  let subtract a b =
30    let result = Int64.sub a b in
31    if result < 0L then
32      Error "Insufficient funds"
33    else
34      Ok result
35  
36  let multiply m factor =
37    Int64.of_float (Int64.to_float m *. factor)
38  
39  let compare = Int64.compare
40  
41  let to_string m =
42    let dollars = Int64.div m 100L in
43    let cents = Int64.rem m 100L in
44    Printf.sprintf "%Ld.%02Ld" dollars cents
45end
46

Benefit: Impossible to mix cents with dollars at compile time

2. Pattern Matching for Business Logic#

ocaml
1(* Account state machine with exhaustive matching *)
2type account_status =
3  | Active
4  | Frozen of freeze_reason
5  | Closed of closure_reason
6
7type freeze_reason =
8  | Suspicious_activity
9  | Court_order
10  | Compliance_hold
11
12type transaction_result =
13  | Success of transaction_id
14  | Rejected of rejection_reason
15
16let process_transaction account transaction =
17  match account.status with
18  | Active ->
19      (* Process normally *)
20      validate_and_execute account transaction
21  | Frozen Suspicious_activity ->
22      Rejected "Account frozen due to suspicious activity"
23  | Frozen Court_order ->
24      Rejected "Account frozen by court order"
25  | Frozen Compliance_hold ->
26      Rejected "Account under compliance review"
27  | Closed reason ->
28      Rejected (Printf.sprintf "Account closed: %s" 
29                  (closure_reason_to_string reason))
30

Benefit: Compiler ensures all cases handled

3. Immutability and Concurrency#

ocaml
1(* Immutable transaction journal *)
2type transaction = {
3  id: transaction_id;
4  timestamp: timestamp;
5  from_account: account_id;
6  to_account: account_id;
7  amount: Money.t;
8  description: string;
9  status: transaction_status;
10}
11
12(* Events are immutable - can never be modified *)
13type event =
14  | Account_created of account_creation
15  | Money_deposited of deposit
16  | Money_withdrawn of withdrawal
17  | Transfer_executed of transfer
18  | Account_frozen of freeze_event
19  | Account_closed of closure_event
20
21(* Event sourcing - reconstruct state from events *)
22let replay_events initial_state events =
23  List.fold_left apply_event initial_state events
24

Benefit: Concurrent access without locks, perfect audit trail

System Architecture#

plaintext
1┌──────────────────────────────────────────────────────┐
2│                     Digital Channels                 │
3│          Mobile App   │   Web   │   API              │
4└──────────────────────────┬───────────────────────────┘
5                           │      REST/GraphQL
6┌──────────────────────────▼───────────────────────────┐
7│                  API Gateway (Kong)                  │
8└──────────────────────────┬───────────────────────────┘
9                           │      gRPC
10       ┌──────────────┬────┴───────┬────────────┐
11       │              │            │            │
12  ┌────▼────┐    ┌────▼────┐  ┌────▼────┐  ┌────▼────┐
13  │ Account │    │ Trans-  │  │ Payment │  │  Loan   │
14  │ Service │    │ action  │  │ Service │  │ Service │
15  │ (OCaml) │    │ (OCaml) │  │ (OCaml) │  │ (OCaml) │
16  └────┬────┘    └────┬────┘  └────┬────┘  └────┬────┘
17       │              │            │            │
18       └──────────────┴────┬───────┴────────────┘
19                           │
20                   ┌───────▼───────┐
21                   │ PostgreSQL    │
22                   │ (Event Store) │
23                   └───────┬───────┘
24                           │
25                ┌──────────┴──────────┐
26                │                     │
27           ┌────▼────┐          ┌─────▼────┐
28           │  Redis  │          │  Kafka   │
29           │ (Cache) │          │ (Events) │
30           └─────────┘          └──────────┘
31

Technical Implementation#

Core Banking Engine (OCaml)#

ocaml
1(* Account module with strong guarantees *)
2module Account : sig
3  type t
4  type error =
5    | Insufficient_funds
6    | Account_frozen
7    | Daily_limit_exceeded
8    | Invalid_amount
9  
10  val create : account_id -> customer_id -> Money.t -> t
11  val balance : t -> Money.t
12  val deposit : t -> Money.t -> (t, error) result
13  val withdraw : t -> Money.t -> (t, error) result
14  val transfer : t -> t -> Money.t -> (t * t, error) result
15  val freeze : t -> freeze_reason -> t
16  val unfreeze : t -> t
17end = struct
18  type t = {
19    id: account_id;
20    customer_id: customer_id;
21    balance: Money.t;
22    status: account_status;
23    daily_withdrawal: Money.t;
24    daily_limit: Money.t;
25    created_at: timestamp;
26    updated_at: timestamp;
27  }
28  
29  type error =
30    | Insufficient_funds
31    | Account_frozen
32    | Daily_limit_exceeded
33    | Invalid_amount
34  
35  let create id customer_id initial_balance = {
36    id;
37    customer_id;
38    balance = initial_balance;
39    status = Active;
40    daily_withdrawal = Money.zero;
41    daily_limit = Money.of_string "5000.00" |> Result.get_ok;
42    created_at = now ();
43    updated_at = now ();
44  }
45  
46  let balance account = account.balance
47  
48  let deposit account amount =
49    if Money.compare amount Money.zero <= 0 then
50      Error Invalid_amount
51    else
52      match account.status with
53      | Active ->
54          let new_balance = Money.add account.balance amount in
55          Ok { account with balance = new_balance; updated_at = now () }
56      | Frozen _ ->
57          Error Account_frozen
58      | Closed _ ->
59          Error Account_frozen
60  
61  let withdraw account amount =
62    match account.status with
63    | Active ->
64        (* Check sufficient funds *)
65        (match Money.subtract account.balance amount with
66        | Error _ -> Error Insufficient_funds
67        | Ok new_balance ->
68            (* Check daily limit *)
69            let new_daily = Money.add account.daily_withdrawal amount in
70            if Money.compare new_daily account.daily_limit > 0 then
71              Error Daily_limit_exceeded
72            else
73              Ok { account with
74                balance = new_balance;
75                daily_withdrawal = new_daily;
76                updated_at = now ();
77              })
78    | Frozen _ -> Error Account_frozen
79    | Closed _ -> Error Account_frozen
80  
81  let transfer from_account to_account amount =
82    match withdraw from_account amount with
83    | Error e -> Error e
84    | Ok from' ->
85        match deposit to_account amount with
86        | Error e -> Error e
87        | Ok to' -> Ok (from', to')
88  
89  let freeze account reason =
90    { account with status = Frozen reason; updated_at = now () }
91  
92  let unfreeze account =
93    { account with status = Active; updated_at = now () }
94end
95

Transaction Processing#

ocaml
1(* Atomic transaction processing with event sourcing *)
2module Transaction_processor = struct
3  type command =
4    | Process_deposit of account_id * Money.t
5    | Process_withdrawal of account_id * Money.t
6    | Process_transfer of account_id * account_id * Money.t
7  
8  type event =
9    | Deposit_processed of transaction_id * account_id * Money.t * timestamp
10    | Withdrawal_processed of transaction_id * account_id * Money.t * timestamp
11    | Transfer_processed of transaction_id * account_id * account_id * Money.t * timestamp
12    | Transaction_failed of transaction_id * string * timestamp
13  
14  (* Process command and generate events *)
15  let process_command db_conn command =
16    Lwt_main.run begin
17      match command with
18      | Process_deposit (account_id, amount) ->
19          let%lwt account = Repository.load_account db_conn account_id in
20          (match Account.deposit account amount with
21          | Ok updated_account ->
22              let event = Deposit_processed (
23                generate_id (),
24                account_id,
25                amount,
26                now ()
27              ) in
28              let%lwt () = Repository.save_account db_conn updated_account in
29              let%lwt () = Repository.append_event db_conn event in
30              Lwt.return (Ok event)
31          | Error e ->
32              let event = Transaction_failed (
33                generate_id (),
34                error_to_string e,
35                now ()
36              ) in
37              let%lwt () = Repository.append_event db_conn event in
38              Lwt.return (Error e))
39      | Process_transfer (from_id, to_id, amount) ->
40          (* Two-phase commit for transfer *)
41          let%lwt from_account = Repository.load_account db_conn from_id in
42          let%lwt to_account = Repository.load_account db_conn to_id in
43          (match Account.transfer from_account to_account amount with
44          | Ok (from', to') ->
45              let event = Transfer_processed (
46                generate_id (),
47                from_id,
48                to_id,
49                amount,
50                now ()
51              ) in
52              let%lwt () = Repository.begin_transaction db_conn in
53              let%lwt () = Repository.save_account db_conn from' in
54              let%lwt () = Repository.save_account db_conn to' in
55              let%lwt () = Repository.append_event db_conn event in
56              let%lwt () = Repository.commit_transaction db_conn in
57              Lwt.return (Ok event)
58          | Error e ->
59              let%lwt () = Repository.rollback_transaction db_conn in
60              Lwt.return (Error e))
61      | _ -> (* other cases *)
62          Lwt.return (Error Account.Invalid_amount)
63    end
64end
65

Migration Strategy#

Phase 1: Parallel Running (3 months)#

  • New system processes transactions in shadow mode
  • Compare results with legacy system
  • Build confidence in correctness
  • Train operations team

Phase 2: Gradual Cutover (6 months)#

  • Migrate accounts in batches (100 accounts/day)
  • New accounts created on new system only
  • Dual write to both systems
  • Automatic reconciliation

Phase 3: Legacy Decommission (3 months)#

  • All traffic routed to new system
  • Legacy system in read-only mode
  • Final data verification
  • Legacy system shutdown

Result: Zero downtime, zero data loss

Results & Impact#

Performance Improvements#

MetricLegacyModernImprovement
Transaction Processing50/sec10,000/sec200x faster
End-of-Day Batch6 hours15 minutes24x faster
API Response TimeN/A50msNew capability
System Uptime99.5%99.99%0.49% improvement
Transaction Cost$0.25$0.0292% reduction

Business Outcomes#

  • $30M annual savings from mainframe decommission
  • 300% increase in mobile banking adoption
  • Real-time payments enabled (Instant Payment System)
  • API platform generating $5M annual revenue
  • Customer satisfaction improved by 45%

New Capabilities Enabled#

✅ Mobile Banking - Modern iOS/Android apps
✅ Real-Time Payments - Instant transfers
✅ Open Banking APIs - Third-party integrations
✅ Digital Wallet - Mobile payments
✅ Personal Finance - AI-powered insights

Technology Stack#

Core Platform#

  • OCaml 5.0 - Core banking logic
  • Lwt - Concurrent I/O
  • Cohttp - HTTP server
  • PostgreSQL - Event store & data
  • Redis - Caching layer
  • Kafka - Event streaming

APIs & Integration#

  • gRPC - Internal services
  • GraphQL - External APIs
  • Kong - API gateway
  • OAuth 2.0 - Authentication

Infrastructure#

  • Kubernetes - Orchestration
  • Docker - Containerization
  • Terraform - Infrastructure as code
  • Prometheus - Monitoring
  • Grafana - Dashboards

Frontend#

  • React - Web application
  • React Native - Mobile apps
  • TypeScript - Type safety

Challenges Overcome#

Challenge 1: COBOL to OCaml Translation#

Problem: 2 million lines of COBOL business logic
Solution:

  • Document COBOL logic before rewrite
  • Automated testing of both systems
  • Property-based testing for equivalence
  • SME review of each module

Challenge 2: Data Migration#

Problem: 50 years of financial data in mainframe
Solution:

  • Custom ETL pipeline
  • Incremental migration with validation
  • Parallel running for verification
  • Automated reconciliation tools

Challenge 3: Team Training#

Problem: No OCaml expertise in-house
Solution:

  • 3-month training program
  • Pair programming with our team
  • Gradual responsibility transfer
  • Comprehensive documentation

Challenge 4: Regulatory Approval#

Problem: Strict regulatory requirements
Solution:

  • Early regulator engagement
  • Comprehensive audit trail
  • Independent security assessment
  • Phased rollout with regulatory checkpoints

Security & Compliance#

Authentication & Authorization#

  • Multi-factor authentication (MFA)
  • Hardware security modules (HSM)
  • Biometric authentication
  • Role-based access control (RBAC)

Data Protection#

  • Encryption at rest (AES-256)
  • Encryption in transit (TLS 1.3)
  • Tokenization of sensitive data
  • PCI-DSS compliance

Audit & Compliance#

  • Complete transaction history
  • Immutable audit logs
  • Regulatory reporting automation
  • SOC 2 Type II certified

Client Testimonial#

"The modernization has transformed our bank. We can now compete with digital banks and fintechs while maintaining the reliability our customers expect. The OCaml platform has been rock-solid - zero unplanned downtime in 18 months of operation."

— CIO, Regional Bank

Key Takeaways#

  1. Type safety matters: OCaml's type system prevented entire classes of bugs
  2. Gradual migration: Parallel running reduced risk dramatically
  3. Event sourcing: Perfect for financial audit requirements
  4. Immutability: Simplified concurrent programming
  5. Training investment: Worth it for long-term maintainability
  6. Regulatory engagement: Early and frequent communication critical

Contact Us#

Modernizing a legacy banking system? Get in touch to discuss your project.


Project Duration: 18 months
Team Size: 15 engineers
Technologies: OCaml, PostgreSQL, Kubernetes
Industry: Banking
Location: United States

RB

Regional Bank

Technical Writer

Regional Bank is a software engineer at NordVarg specializing in high-performance financial systems and type-safe programming.

OCamlPostgreSQLKubernetesgRPCReact

Join 1,000+ Engineers

Get weekly insights on building high-performance financial systems, latest industry trends, and expert tips delivered straight to your inbox.

✓Weekly articles
✓Industry insights
✓No spam, ever

Related Case Studies

Cryptocurrency Exchange Platform
Cryptocurrency • Digital Asset Trading Company

Build secure, high-performance trading platform handling $2B daily volume

RustPostgreSQLRedisKubernetes
View case study
AI-Powered Insurance Claims Processing
Insurance • Major Insurance Provider

Manual claims processing taking 14 days with 12% error rate

PythonTensorFlowPostgreSQLApache Airflow
View case study

Ready to Transform Your Systems?

Let's discuss how we can help you achieve similar results with high-performance, type-safe solutions tailored to your needs.