Building an Enterprise-less

online bank





Anton Keks / @antonkeks / Codeborne

A bit of history

Codeborne

Founded in 2010 by former Swedbank developers

Swedbank

Acquired Hansabank, a very innovative bank of 2000s

After 2008 closed many branches/ATMs

In Estonia, most banking business is online

Demos

Swedbank

Bank Saint-Petersburg

Ural Reconstruction and Development Bank

GlobalFinance / Markswebb awards

Hand-crafted code/frameworks


produce simpler code

easier to customize

more reliable

you can fix it in case of urgent problems

The Enterprise (thinking)


Makes you write more code

Makes your app slow

Makes debugging hard

Makes sysadmin's life harder

If it breaks, it is broken

Ibank

A platform for online banks

Private + Corporate + Mobile + Web site

Java 8 / Play Framework 1.3

Play framework

Rails, Django inspired

We use newly-released v1.3

not 2.x, which is for Scala

Why Play is cool?

Fresh, no bullshit, anti-enterprise

Writing few code that also works

Full-stack, quick start

Influenced our architecture a lot

In more detail

Code reloads

"Hit refresh" workflow

(You can use springloaded in your own project)

Play 2 is much slower at that

Disadvantage: it recompiles code itself

Good/Bad: so-called "Magic", or enhancers

Tries to make Java a better language

Properties, less boilerplate

Config files with support for environments


db.url=jdbc:h2:mem
%prod.db.url=jdbc:oracle:thin:blahblah
    

Localization files (multilanguage support)

Own HTTP server (based on Netty)

play start
play stop

No even servlets :-)

Sessions

Signed cookies

String-only up to 4kb

No server state

Deployment during working hours!

Better back button support

+ Flash

Async support

Inspired by Node.js

await(WS.url("http://blah").getAsync())

Returns request thread to the pool

Serves all static files asynchronously

But, hard to call DB stored procedures that way

Jobs

For asynchronous stuff

And processing (e.g. deferred and recurrent payments)



      new DeferredPaymentProcessor(payment).now();
    

or


@Every("5mn")
public class DeferredPaymentProcessor extends Job { ... }
    

Modules

Dependency management (using ivy)

Many 3rd-party ones (e.g. pdf, excel)

And we write our own (logging, testing, cms)

Plugins

Most stuff can be customized/redefined

e.g. ModuleInheritingPlugin

Alive and open-source @github

Client side

MVC is much easier than single-page apps

If needed, write modular JS/AJAX

Less & Autoprefixer for CSS


.book {
  padding-top: @defaultPadding + 10px;

  .title {
    font-weight: bold;
    transform: scale(1.2);
  }
}
  

DB

ActiveRecord pattern on top of JPA/Hibernate


User user = User.find("byUsername").first();
user.lastLoginTime = now();
user.save();
    

save() must be explicit

Hibernate in JPA mode (no detached save)

@DynamicUpdate

DB migration

Play evolutions are good

We use Liquibase (via plugin)


<changeSet id="123" author="Codeborne">
  <addColumn table="users">
    <column name="lastLoginAt" type="timestamp"/>
  </addColumn>
</changeSet>
    

Supports almost any database

We share most (but not all) changesets between banks

Changesets must be backwards-compatible

Services

Necessary evil

Abstraction layer between ibank and the backend(s)

ibank -> service -> banking system

MVC -> MVSC :-)

Guice


@Inject CardService cardService;
...
List<Card> cards = cardService.activeCards(customer);
    

class BankOne extends Module {
  public void configure() {
    bind(CardService.class).to(BankOneBackendCardService.class);
  }
}
    

class Demo extends Module {
  public void configure() {
    bind(CardService.class).to(DemoCardService.class);
  }
}
    

Services are non-abstract classes

Integrations, customizations

Integrations

In every bank, there are plenty:

Core system (ABS)

Card/processing system

CRM

Forex

Customer support

Loyalty program

Partners

etc, etc, etc

More integrations - harder to be reliable

Parallel development is hard with non-agile devs

How do we do it?

  1. We write higher-level APIs for protocols first
  2. Then implement business logic using this API/DSL

SOAP client

soapService.request("Cards").param("CustomerID", 123L).send();

No code generation!

No problems with SOAP 1.0, 1.1, 1.2 interoperation

Stored procedures

dbService.query("get_customer", 123L);

Unfortunately, many of these (over)use XML

DOM and xpath are slow: custom SAX-based XML parser


new XMLParser((path, value) -> {
  switch (path) {
    case "messages/message": message = new Message(); break;
    case "messages/message/@id": message.id = value; break;
    case "messages/message/subject": message.subject = value; break;
  }
}).parse(xml);
    

Template-based generation



  ${beneficiaryAccount}
  ${beneficiaryName}
  ${amount}
  ${currency}

    

You can even use it for more bizarre SOAP requests :-)

TDI

(Unit-)Test Driven Integration

Almost like TDD


  1. Call a real service (black box)
  2. Save the response
  3. Write a test for parsing of it
  4. Write code to make test pass

"Standard" Rest API

Alternative inverse integration

(Bank implements our spec)


GET /customers/123
GET /customers/123/accounts
GET /customers/123/accounts/123123123123/transactions
    

Automated testing tool with warnings and errors

Hard to make changes in the spec

Problems with backends

Backends are slow

Caching via Guice interceptors

bindInterceptor(any(), annotatedWith(CacheFor.class), this);

Memcached

Prefetching jobs

Backends can be offline

Fault-protection

Health monitor

Temporary storage

Deferred executor jobs

Backends have partial data

Merge data from multiple sources

All in the name of UX

Customization

CSS first

Services

You can override any file in sub-module

You can write 100% custom code

No architectural restrictions

(besides discouraging of copy-paste)

Testing

Essential part of knowing that your code (still) works

Good: testing is part of Play

Bad: Rails-style "boot-up-everything" tests

Slow + Not great for TDD

Unit + UI

1500 tests, 4-5 mins

Run all during every build

Optimizations + Parallel execution

Unit tests

Fast, preferred

Business logic and all special cases

Mockito helps


userService = mock(UserService.class, RETURNS_DEEP_STUBS);
when(userService.user("bob").getFullName()).thenReturn("John");
    

UI tests

Slower, for "happy paths"

Selenide helps


$(By.name("username")).val("bob");
$(By.name("password")).val("secret");
$("#login-button").click();
$(".page.overview").should(appear);
    

In-memory DB: H2

Play supports initial data via Fixtures

Restore state before each test

Demo integration

Logging

Most developers don't log properly

Good logs are

Compact

Machine-readable

Completely traceable

Multiple files

  • request.log
  • security.log
  • services.log
  • general.log

Logging tips

Request ID / Thread ID

Durations

Java stack traces only when needed

Play: Self4J over Log4J

grep/awk are your best friends

Build process

Jenkins

Compile + compress resources + unit tests + ui tests

Any green build can go to production

Deployment

2 servers

Stateless: deployment during working hours

Simple DNS-based load balancing

No loss of sessions

Migration

Most of our customers had a previous online bank

With some users

Transition period

Continuous data migration

Embedded/live documentation e.g. banklink protocol description

CMS

git-based

play-web @github

Thank you!


Play modules & Selenide: github.com/codeborne

Slides: angryziber.github.io/slides

Sounds fun? job@codeborne.com


Anton Keks / @antonkeks / Codeborne