A single server and a database is enough here

Ten years ago a system design conversation was short. A server, a monolith, Postgres, maybe a load balancer in front. For most products that was the entire architecture, and it held. Now the same conversation opens with API gateways and doesn’t stop: Kubernetes, service meshes, Kafka, Redis, three databases because each one is good at one thing, gRPC, GraphQL, Elasticsearch, Terraform, Prometheus, tracing, sharding, replication, autoscaling. The awkward part is that none of it is junk. Every one of those tools exists because someone hit a real wall: more users, more data, more teams, more regions, more nines of uptime. ...

September 1, 2026 · 1 min · Murat Useinov

Reading PHP 8.6 RFCs as constraints

Accepted, declined, discussion drama. That is how most posts about PHP 8.6 RFCs read, and the vote cycle is in full swing this month. I read RFCs for a different reason. Not “what do I get”, but “what will my current code look like next year”. One RFC is a feature. Ten RFCs are a direction. When several push the same way, toward stricter types, explicit declarations, deprecating some old freedom, the direction tells you which of your habits are becoming legacy while you still type them. Syntax sugar is the least interesting part. Deprecations are the most interesting, because every deprecation is a migration with a countdown attached. ...

August 17, 2026 · 2 min · Murat Useinov

A worker is not a controller

All the logic inside execute(), exit code always zero, no lifecycle thinking at all. A controller without a request. I still see this console command in every second codebase. Symfony stopped being only an HTTP framework a long time ago, but habits are slower than releases, and with 8.1 giving console and workers first-class attention the excuse is gone. An HTTP request lives for milliseconds. The kernel builds services, handles, throws everything away. A worker lives for hours. Same container, very different lifecycle. Every service that quietly keeps state, an in-memory cache, an accumulating buffer, an entity manager full of tracked objects, is invisible in HTTP and becomes a memory leak in a worker. If your consumer needs a nightly restart by cron, this is where the night went. ...

May 25, 2026 · 2 min · Murat Useinov

An LLM call is just IO

One method, response back. Laravel 13 makes calling a model feel like calling a mailer, and the demo level is now twenty minutes of work. Which is exactly when architecture starts to matter, because everybody will ship the demo. An LLM call is IO. Slow IO, expensive IO, IO that sometimes fails and sometimes lies. We know how to handle IO like that. We forget, because the response text looks smart and the API looks native to the framework. ...

March 11, 2026 · 2 min · Murat Useinov

From Kohana to PHP 8.5

A Kohana ORM, an N+1 on a catalog page, and a blog post about it. That was November 2014. It is December 2025, the code around me runs PHP 8.5, and I went back and read that first post. The surface changed completely. A 2014 controller was a fat method: it read the request, validated by hand, called the ORM, formatted the response, sometimes sent an email on the way out. Types lived in docblocks and in hope. Deploy was files over FTP and a prayer. Today the same feature is a typed handler with constructor injection, validation at the boundary, a use case object, a message on a queue for the email, a trace id through the whole thing. The engine checks what a comment used to promise. I do not miss the old way. ...

December 11, 2025 · 2 min · Murat Useinov

Observability has a budget

Last year we turned tracing on everywhere. This year the telemetry invoice is a line item a manager asks about by name. The dashboards did not get more useful. More data, same confusion at 2 a.m. Time to design instead of collect. The design starts from the questions I actually ask when the pager goes off. In practice there are four: is the latency of key endpoints normal, did the error rate jump, how far behind are the queues, is the database saturated. That is the core set. Metrics for these must be cheap, always on, with alerts. Everything else can be sampled traces I pull up on demand. ...

March 10, 2025 · 2 min · Murat Useinov

PHP 8.4 after the hype

A private field, a getter, a setter with one line of normalization. Three members for one idea, repeated through every entity folder I have ever opened. PHP 8.4 is two months old, the conference demos are done, and the honest question is which features earn a place in a working codebase. My filter is simple. A feature is good when it deletes code. Property hooks pass: class User { public string $email { set => strtolower(trim($value)); } } The class is shorter and the normalization cannot be bypassed. But keep hooks boring. A hook that talks to the database is a magic getter from 2008 in new syntax. ...

January 15, 2025 · 2 min · Murat Useinov

Property hooks, before the hype settles

Six lines of getter and setter ceremony around one strtolower. PHP 8.4 is in release candidates, and property hooks delete exactly that: class Customer { public string $email { set => strtolower(trim($value)); } } Assignment stays assignment, $customer->email = $input, and normalization happens on the way in. For this exact case, one value, no dependencies, I am convinced. The property remains a property. Where I get careful is everything past normalization. A setter method is ugly, but it announces itself. setEmail() in a call stack tells you code ran. $obj->email = $x looks free, and with hooks it is not. Put validation that throws into a hook, and every plain assignment becomes a possible exception, invisible at the call site. Put a side effect in, and you have built magic the next person discovers through a debugger at midnight. We spent years removing __get and __set from codebases for this exact crime. Hooks are their respectable cousins, better typed, analyzable, and the temptation is identical. ...

October 14, 2024 · 2 min · Murat Useinov

WebSockets change your operational model

Every client reconnects at once. That is what a deploy means now on one project of mine, since Reverb made WebSockets a first-party Laravel feature this spring and order updates go to the browser instead of polling. The code was the easy part. A classic PHP app is stateless between requests. An FPM worker takes a request, answers, forgets. Capacity planning is requests per second. A WebSocket server is the opposite animal: thousands of open connections that mostly do nothing, but each one holds memory and a file descriptor, and each one is state that dies with the process. Your quiet realtime feature has a thundering herd built in. Plan for reconnect storms, raise descriptor limits, and make the client reconnect with jitter, never on a fixed timer. ...

July 3, 2024 · 2 min · Murat Useinov

Laravel 11 removes files, not decisions

The diff of a Laravel 11 upgrade on one mid-size API this week is mostly deletions. Almost empty app/, configuration in one fluent bootstrap file, the slim skeleton I wrote about in January now real. It feels great. Now look at what survived untouched. The use case class that wraps an order state change in a transaction. The listener that must fire only after commit, because it queues an email about a row that must exist. The cache invalidation that follows every write to a heavily read table. The decision that validation lives in a request class and business rules one layer deeper. None of this is skeleton. None of it got smaller. ...

March 25, 2024 · 2 min · Murat Useinov