advanced 2 min answer

A design platform's rendering service was built for single-page exports. Over three years it accumulated video, animation, bulk export and print output - each bolted onto the same synchronous request path. Exports now time out unpredictably. How should evolutionary architecture have prevented this, and what do you do now?

evolutionary-architecturerenderingcanvaasyncdebugging
Show the full answer Hide the answer

What actually happened

The original design encoded an assumption that never got written down: rendering completes within one request. That assumption was true for a single page and false for a 200-page document, a two-minute animation and a print-resolution export — but the assumption lived only in the shape of the code, so nothing objected as each new output type was added.

This is the ordinary way architectures decay. Not a bad decision, but a good decision whose justification expired silently.

What evolutionary architecture would have done differently

Name the assumption and guard it. An evolutionary architecture makes the load-bearing properties executable. Here that means a fitness function: no render exceeds N seconds on the synchronous path. The first export type that violated it would have failed a build rather than a customer.

Make the expensive direction the default direction. Introduce the asynchronous path when the first long-running output type appears, not when the third one breaks. The cost of adding a job queue at that point is a sprint; the cost now is a migration of every client.

Design for last responsible moment, not for no moment. Evolutionary does not mean unplanned. It means you deliberately defer the decision and you define the signal that says the moment arrived. The missing artefact was the signal.

What to do now

  1. Split by duration, not by feature. One classifier at the entry point: if the render is predicted to exceed the synchronous budget, it becomes a job. Prediction can be crude at first — output type and page count get you most of the way.
  2. Give the async path a real contract. Submit returns a job handle; status is polled or pushed; the artefact lands in object storage with a signed URL. Clients migrate one output type at a time.
  3. Keep the synchronous path for the cases that fit, and enforce the budget with a hard timeout so it cannot silently re-acquire long work.
  4. Add the fitness function retroactively so this class of drift fails in CI rather than in production.

The lesson

Every architecture is a set of assumptions. Evolutionary architecture is not the absence of assumptions — it is the practice of writing them down as automated checks so you find out the day they stop being true.