Skip to content
back to the discussion
Discussion summary2024

Case Roulette: Examples of Bad Architecture

The sixth season of Podlodka Techlead Crew runs a case roulette: hosts Igor Antonov and Pavel read out theses while three experts — Alexander, chief technology officer at Tinkoff, Ilya Zonov, an architect in Tinkoff Business, and Kirill, a staff engineer at Sbermarket — swap war stories about universal architecture, CQRS, GraphQL, ORMs, in-house standards and microservices.

Podlodka Techlead Crew9 min read

This summary is shorter than the full discussion; examples and assessments reflect the participants’ experience and views.

The main thread of the material
01

Good architecture and universal solutions

The conversation opens with definitions. Kirill gives the classic answer: good architecture lets the business run, meets functional and non-functional requirements and stays flexible — change arrives faster than you can refactor, so what matters is moving fast to a new scheme, not a perfect state. Alexander recalls that architecture books open with pages of competing definitions — components, links, the properties that matter — and that an architecture built for any future change turns out too abstract and useless in your current domain: in half a year today's good decision stops being good. Igor remembers an article on IBM's site whose author counted around 154 definitions of software architecture. Ilya notes that architecture always imposes constraints and the point is controlling them — fitness functions come up in passing. Pavel admits his team never managed a definition and listed what architecture affects: reliability, cost of ownership, resource efficiency, time to market — at least those are measurable.

The first case is universal architecture, the thing promised in system design interviews. Kirill jokes that if anything can be written into any column and finished off with a macro, you do not need developers — take Excel. Alexander recalls a customer some seventeen years ago who, after hearing about the domain model and MVC, asked for a universal controller wiring up any business logic. The second example is Amazon Prime Video: video processing assembled the canonical way, on Step Functions and S3, came out an order of magnitude dearer than processing video in memory. Past its limits of applicability a universal solution fails, like an engineer with one hammer driving screws. Ilya cites an audit of form-building engines: a form for an operator and one for a customer do not reduce to each other — different goals, requirements and audiences. His own miss was putting everything, television schedules included, into a universal time-series store on top of Cassandra; the data later moved to a relational database. Kirill defends the penknife: useless on a building site, a lifesaver in the forest, and at MVP stage a flexible or no-code application helps the business.

02

CQRS and GraphQL: the limits of hype

The next thesis is when CQRS does harm. Kirill points to Vlad Khononov's book on domain-driven design and its flowchart: whether you need CQRS, or a transactional script will do, depends on the type of domain — core, supporting or generic; in a supporting domain the pattern only adds complexity and eats into time to market. Alexander says he cannot recall CQRS built from the start: it answers pain, once an application has grown into a mass of updates, reads and multi-level caching, where separating commands from read views yields a system an order of magnitude faster. Doing it upfront risks premature optimisation for a hyped technology, so he answers such proposals with a lot of questions. Kirill unpacks three levels: command-query separation in code he uses every day, splitting into two databases helped once real load arrived, and the pattern proper he applies to search across the data of several transactional services. Pavel notes that events arrive late and break consistency.

On GraphQL, Ilya says honestly that on one project it worked fine, and that nobody in the group gathered here has gone for that hype. Alexander walks through its origin: the bet on the mobile web failed, and collecting data for native applications from a pile of APIs was hard — so GraphQL appeared. In his view you might as well expose SQL to the internet: the declarative language is lovely for the front end, but on the back end complexity is hard to control, because the client decides what to ask for. On his special projects GraphQL was plugged in with the Prisma engine, single-threaded in that version and locking the data sources: the application fell over and stayed down, the engine was ripped out, and the approach turned into a cargo cult. Kirill describes a fresh hypothesis — for analytics, not transactional scenarios, GraphQL lets a consumer read the fields it needs from any service — but advises against it under load. Ilya used it to avoid dragging bulky credit applications around.

03

Repositories, standards and microservices

Kirill settles the ORM versus repository argument at once: they are different patterns. An ORM shines on changes to an aggregate — if a basket had items added, changed and removed and its address edited, it works out what changed and saves it in one transaction; reads need no objects, and a plain query into a DTO is cheaper. The repository owns integrity: one aggregate, one repository, otherwise somebody will eventually forget to save the basket's status — while in read queries it becomes a cargo cult. Alexander recalls rewriting the storage layer of a loaded site over ten years ago, running on four machines in one rack: before launch the contractors had hung clustered indexes on the tables, vacuum finished the job, degradation showed on the dashboards, and repositories with a cache refreshed at save time were the rescue. Pavel adds that on somebody else's project an ORM shrinks uncertainty, while in his own code he prefers plain SQL and knows whether a query hits an index.

On in-house standards Alexander quotes the proverb about not bringing your own rules into someone else's monastery: unifying ten existing standards produces an eleventh. The upside is tooling and moving people between projects, as Google's readability process does; the downside is that without consensus people stop believing standards exist. Ilya adds that without tooling and explicit benefits a standard dies quickly, and Kirill, who owns standards at his company, advises taking existing practice in ninety per cent of cases. The last thesis is microservices at the start. Ilya launched leasing as a monolith with boundaries inside it because the domain was poorly understood — and some boundaries still came out wrong. Alexander's partner service was sliced by entity, and the team later glued ten or twelve services back into three; in another story an admin panel holding everything at once took the application down, the split took half a year, the shared Rancher could not cope and declared hosts unavailable, and containers came up with docker-compose on the old machines. Kirill asked a team of four why they had sixteen microservices: by the time he left, two remained.

Takeaways

What to take away

  1. 01Universality lasts only to the limits of applicability: the canonical pipeline on Step Functions and S3 cost Amazon Prime Video an order of magnitude more than processing video in memory.
  2. 02CQRS is rarely built from scratch: command-query separation in code is cheap and always useful, splitting the databases answers load that has actually arrived, and the full pattern earns its place for search across several services.
  3. 03An ORM wins on changes to an aggregate while reads are cheaper with a plain query; a repository belongs where there is a domain model, and turns into a cargo cult in read queries.
  4. 04Microservices divide ownership between teams rather than speed development up: sixteen services for four people, and ten or twelve glued back into three, are the price of slicing before the domain is understood.

Sources