Skip to content
back to the presentation
Talk summary2019Fellow

Evolution of tinkoff.ru over 3 years

At ArchDays 2019 Alexander Polomodov walks through three years of evolution in the unauthenticated zone of tinkoff.ru — everything a visitor sees before the personal account. The talk covers three systems: the frontend application, the content backend with content management, and the personalisation and testing service. Traffic balancing and ad buying stayed out of scope.

ArchDays 20199 min read

This summary is shorter than the full talk; examples and assessments reflect the author’s experience and views.

The main thread of the material
01

Frontend: monolith, components, framework

The first system is the tinkoff.ru frontend: as many external users as people who reach the site, almost no internal ones, many dependencies on other APIs, and a load Alexander calls noticeable but short of high-load. Three years ago it was a monolithic React application: one repository, around fifty people writing code in it, and a release that took almost a month when he joined, instead of the promised two or three weeks. His own team of three owned product forms and simple landing pages. Step zero, he says, always looks the same: meet the team, learn the processes, show a tactical plan. Step one was picking the components development would revolve around: the monolith itself, an engine assembling pages from blocks, a new forms engine replacing one that had hit its limits, and a new tracking library, because business customers compare variants by conversion and want the events.

Then the monolith was split from the inside: product teams' code moved into separate folders, and shared components were pulled out into dependencies by the common architecture team. At the same time the new components were plugged back into the monolith as dependencies: results show up quickly, and the business need not sign off an expensive migration with an unpredictable outcome. Alexander compares it to the shell of an old Soviet car with completely different internals. The last step was a framework of their own, holding their own work and what was extracted from the monolith, so every product team got its own application and repository on top of shared dependencies. It was proven on an important team where business value shows fastest: a copy of the application built the new way went into a test, traffic was split roughly evenly, and more events registered because the new application was faster. The lesson: do not freeze halfway, with new components living inside the old monolith.

02

Content backend: from AngularJS to Kubernetes

The second system prepares content for the frontend and the mobile applications, while internal users edit it through admin panels; by the end of the story the team was about fifteen people. Three years ago it was a monolithic AngularJS application: it began as an API somebody built for themselves on a familiar stack, so the codebase stayed small and the team was three people. In a year it shipped two releases, with no backward compatibility, versioning, logs or monitoring — none of what a backend ought to have. The main internal customers were the web analysts, who needed to change product content without a frontend release, and those took weeks. Step one was stabilisation: the system was classified as legacy, dependent teams suffered, and coupling was so strong that a change for a minor customer landed on a major one. Then the split: a content backend on Node.js, because React developers were plentiful nearby, and PostgreSQL instead of the inherited database.

An architecture team was set up for the hard decisions — and, in Alexander's words, it ran wild: it designed a big house, while product teams handed out a service per developer, each a unique work of art. There was nowhere to deploy dozens of them, so they lived on docker-compose; he says he covered those problems in a separate talk about a month and a half before ArchDays. The architecture team was disbanded in that form, the services were brought in line with the twelve factors and Kubernetes, and the engineers proposed a default backend configured with YAML instead of programming, plus a simple BFF: from outside the services became almost identical. This year some moved off their own machines into Kubernetes and Google's cloud — allowed, because they hold page content, not personal data. The monolith is still alive: another team still owes the last piece of functionality, and only then can the system be switched off — Alexander dreams of printing its source and grilling kebabs over it.

03

Personalisation and questions from the floor

The third system is small: it enriches the request with geography and device data, pulls up what is known about the user — client or not, which products, which abandoned applications — and decides what to show. It has one external consumer, the content backend, plenty of requests and a team of about five. It started with an off-the-shelf product whose name Alexander could not recall and an in-house C++ engine written by a single developer; their own system was unstable and did not match the box's feature set. The team was expanded, the MVP finished, the licence not renewed — but, as he puts it, they wanted a car and had so far built a bicycle: the prototype was never designed to grow, and the interface, built separately, did not fit the scenario where a user creates a page and configures personalisation there. The engine was rewritten from scratch as a UNIX-style pipeline of steps, and the interface was refactored and squeezed with difficulty into the content management circuit.

Hence the conclusion: build an MVP with whatever you like, but plan to throw it away, and remember Conway's law — the system grew in a separate team under a separate manager and later integrated like a surgical scar. Asked why the backend scheme worked, he explained that the business would never have waited a year for microservices: the old system was still patched and fed urgent features while the replacement grew alongside behind a facade repeating the old API. On rolling out shared changes: frontend page blocks switch by configuration on the fly, while the backend wants fitness functions instead of an epic with a pile of tickets — a rule, a warning in the pipeline for a couple of weeks, then a blocked release. Alexander refused to estimate the overrun: you cannot plan without knowing the depth of the rabbit hole. A flexible DSL for field relations in the first forms engine was barely used and cut as over-engineering. Releases are counted across all the applications: some ten frontend teams add up to one or two a day, though a single team needs about a week to gather a batch of features. There are no distributed transactions, which he calls a good thing.

Takeaways

What to take away

  1. 01System size dictates the strategy: the small personalisation service was rewritten from scratch, the mid-sized backend was overtaken by parallel development behind a facade over the old API, and the large frontend changed piece by piece as new components went into the monolith.
  2. 02Injecting new components into the old monolith gives fast feedback, but it is a waypoint rather than a destination: the goal is separate applications and repositories per product team on top of a shared framework.
  3. 03Microservices should start with rules and an operations plan: without agreements every service comes out unique, and splitting by entity instead of context boundaries produces inconsistent data and requests to merge everything back.
  4. 04An MVP can be built with anything as long as it is planned as throwaway: a prototype kept alive as a product costs more than an engine rewritten cleanly.

Sources