<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Architecture on Murat Useinov</title>
    <link>https://useinov.com/tags/architecture/</link>
    <description>Recent content in Architecture on Murat Useinov</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 01 Sep 2026 19:40:00 +0300</lastBuildDate>
    <atom:link href="https://useinov.com/tags/architecture/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>A single server and a database is enough here</title>
      <link>https://useinov.com/posts/2026/single-server-is-enough/</link>
      <pubDate>Tue, 01 Sep 2026 19:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2026/single-server-is-enough/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Now the same conversation opens with API gateways and doesn&amp;rsquo;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Reading PHP 8.6 RFCs as constraints</title>
      <link>https://useinov.com/posts/2026/php-86-rfcs/</link>
      <pubDate>Mon, 17 Aug 2026 21:05:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2026/php-86-rfcs/</guid>
      <description>&lt;p&gt;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 &amp;ldquo;what do I get&amp;rdquo;, but &amp;ldquo;what will my current code look like next year&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>A worker is not a controller</title>
      <link>https://useinov.com/posts/2026/worker-not-controller/</link>
      <pubDate>Mon, 25 May 2026 21:25:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2026/worker-not-controller/</guid>
      <description>&lt;p&gt;All the logic inside &lt;code&gt;execute()&lt;/code&gt;, 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>An LLM call is just IO</title>
      <link>https://useinov.com/posts/2026/llm-call-is-io/</link>
      <pubDate>Wed, 11 Mar 2026 20:15:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2026/llm-call-is-io/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>From Kohana to PHP 8.5</title>
      <link>https://useinov.com/posts/2025/eleven-years-later/</link>
      <pubDate>Thu, 11 Dec 2025 21:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2025/eleven-years-later/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Observability has a budget</title>
      <link>https://useinov.com/posts/2025/observability-budget/</link>
      <pubDate>Mon, 10 Mar 2025 22:30:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2025/observability-budget/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Time to design instead of collect.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>PHP 8.4 after the hype</title>
      <link>https://useinov.com/posts/2025/php84-in-production/</link>
      <pubDate>Wed, 15 Jan 2025 15:50:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2025/php84-in-production/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;My filter is simple. A feature is good when it deletes code.&lt;/p&gt;
&lt;p&gt;Property hooks pass:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;User&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$email&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;set&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;strtolower&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;trim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Property hooks, before the hype settles</title>
      <link>https://useinov.com/posts/2024/property-hooks/</link>
      <pubDate>Mon, 14 Oct 2024 20:50:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2024/property-hooks/</guid>
      <description>&lt;p&gt;Six lines of getter and setter ceremony around one &lt;code&gt;strtolower&lt;/code&gt;. PHP 8.4 is in release candidates, and property hooks delete exactly that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Customer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$email&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;set&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;strtolower&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;trim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Assignment stays assignment, &lt;code&gt;$customer-&amp;gt;email = $input&lt;/code&gt;, and normalization happens on the way in. For this exact case, one value, no dependencies, I am convinced. The property remains a property.&lt;/p&gt;
&lt;p&gt;Where I get careful is everything past normalization. A setter method is ugly, but it announces itself. &lt;code&gt;setEmail()&lt;/code&gt; in a call stack tells you code ran. &lt;code&gt;$obj-&amp;gt;email = $x&lt;/code&gt; 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 &lt;code&gt;__get&lt;/code&gt; and &lt;code&gt;__set&lt;/code&gt; from codebases for this exact crime. Hooks are their respectable cousins, better typed, analyzable, and the temptation is identical.&lt;/p&gt;</description>
    </item>
    <item>
      <title>WebSockets change your operational model</title>
      <link>https://useinov.com/posts/2024/reverb-operations/</link>
      <pubDate>Wed, 03 Jul 2024 23:10:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2024/reverb-operations/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Laravel 11 removes files, not decisions</title>
      <link>https://useinov.com/posts/2024/laravel-eleven/</link>
      <pubDate>Mon, 25 Mar 2024 23:05:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2024/laravel-eleven/</guid>
      <description>&lt;p&gt;The diff of a Laravel 11 upgrade on one mid-size API this week is mostly deletions. Almost empty &lt;code&gt;app/&lt;/code&gt;, configuration in one fluent bootstrap file, the slim skeleton I wrote about in January now real. It feels great.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>The shrinking Laravel skeleton</title>
      <link>https://useinov.com/posts/2024/laravel-slim-skeleton/</link>
      <pubDate>Sat, 06 Jan 2024 22:25:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2024/laravel-slim-skeleton/</guid>
      <description>&lt;p&gt;No &lt;code&gt;app/Http/Kernel.php&lt;/code&gt;. That is the first thing you notice in the Laravel 11 preview, and the first thing every comment thread asks: where did my middleware go.&lt;/p&gt;
&lt;p&gt;The skeleton is the loudest change this quarter, louder than any feature. An &lt;code&gt;app/&lt;/code&gt; directory that is almost empty, no folder of middleware stubs, a pile of service providers collapsed into one, config files trimmed.&lt;/p&gt;
&lt;p&gt;The middleware did not go anywhere. It moved into the framework. The mechanism exists exactly as before, only the file does not, and &lt;code&gt;bootstrap/app.php&lt;/code&gt; gets a small fluent API to reconfigure the stack when you actually need to.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Outbox: events that do not lie</title>
      <link>https://useinov.com/posts/2023/transactional-outbox/</link>
      <pubDate>Wed, 07 Jun 2023 20:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2023/transactional-outbox/</guid>
      <description>&lt;p&gt;An email confirming an order that does not exist. A colleague showed me this one last week, together with the mirror bug: an order that exists and never got its email.&lt;/p&gt;
&lt;p&gt;The code was the obvious kind. Commit the Doctrine transaction, then dispatch to the AMQP transport. Two systems, two writes, no shared transaction. Publish after commit and the process can die between them, event lost. Publish inside the transaction and the broker gets an event for data that may still roll back. Both orders are wrong, and under real load this fires weekly.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Persistent PHP workers need discipline</title>
      <link>https://useinov.com/posts/2023/persistent-worker-discipline/</link>
      <pubDate>Mon, 03 Apr 2023 15:45:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2023/persistent-worker-discipline/</guid>
      <description>&lt;p&gt;A static array with the comment &amp;ldquo;cache, cleared per request&amp;rdquo;. It went to production on RoadRunner last month, and the memory graph turned into a staircase.&lt;/p&gt;
&lt;p&gt;PHP had one superpower nobody advertised: the request died. Every leak, every forgotten static, every open transaction was erased when the process shut down. Shared-nothing was not architecture. It was amnesia, and amnesia forgave us everything.&lt;/p&gt;
&lt;p&gt;RoadRunner and Swoole take it away. The worker lives for thousands of requests, and the class of bugs changes on the first day. That &amp;ldquo;per request&amp;rdquo; cache became a cache per worker lifetime, and the supervisor killed the process when it ran out of memory. A logger kept the request id in a property, so entries from user B carried the id of user A. And the best one: an exception in the middle of a Doctrine transaction left the connection with the transaction open, and the next request on that worker silently joined it.&lt;/p&gt;</description>
    </item>
    <item>
      <title>DI attributes: config moves into the class</title>
      <link>https://useinov.com/posts/2023/symfony-di-attributes/</link>
      <pubDate>Mon, 27 Mar 2023 23:10:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2023/symfony-di-attributes/</guid>
      <description>&lt;p&gt;Three lines of YAML in a file two directories away, for one integer that one service reads. That was the first thing I converted when I started moving a project&amp;rsquo;s service config from YAML into attributes, to see where the line is.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;__construct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;#[&lt;/span&gt;&lt;span class=&#34;nd&#34;&gt;Autowire&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;%env(int:IMPORT_BATCH_SIZE)%&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$batchSize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the class tells you everything about itself. Same with &lt;code&gt;#[TaggedIterator]&lt;/code&gt; for collecting all implementations of an interface, and &lt;code&gt;#[AsDecorator]&lt;/code&gt; for wrapping a service. These are local facts. A local fact belongs next to the code it describes. When I open the class, I want to stop searching.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Readonly classes for value objects</title>
      <link>https://useinov.com/posts/2022/readonly-classes/</link>
      <pubDate>Tue, 08 Nov 2022 22:45:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2022/readonly-classes/</guid>
      <description>&lt;p&gt;Two &lt;code&gt;readonly&lt;/code&gt; keywords for two properties in a two-property class. That is what an immutable &lt;code&gt;Money&lt;/code&gt; looks like in PHP 8.1:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;final&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Money&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;__construct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;readonly&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;readonly&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$currency&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;PHP 8.2 is at release candidate stage, and the feature I am waiting for is this one. The keyword moves up and says it once:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;final&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;readonly&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Money&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;__construct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$currency&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every property is readonly, and the class refuses dynamic properties on top. Cosmetics, yes. But cosmetics that make the right thing the short thing, and that changes what people actually write.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Argument resolvers and the HTTP border</title>
      <link>https://useinov.com/posts/2022/argument-resolver-dto/</link>
      <pubDate>Mon, 13 Jun 2022 20:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2022/argument-resolver-dto/</guid>
      <description>&lt;p&gt;Forty lines of &lt;code&gt;$request-&amp;gt;get()&lt;/code&gt; before the first line of real logic. That was the controller nobody wanted to touch, and the Symfony 6.1 upgrade was my excuse to open it.&lt;/p&gt;
&lt;p&gt;An action that reads ten request fields by hand does two jobs. It translates HTTP into data, and it runs the use case. The first job is boring and repeated in every action, which is exactly why it should not be written by hand ten times. Symfony has argument resolvers for this since 3.1. People just do not use them for their own types.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Redis 7 functions, same old rule</title>
      <link>https://useinov.com/posts/2022/redis-seven-functions/</link>
      <pubDate>Thu, 05 May 2022 20:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2022/redis-seven-functions/</guid>
      <description>&lt;p&gt;&lt;code&gt;FCALL update_if_newer 1 user:42 17&lt;/code&gt;. One round trip, a name instead of a SHA, and it survives a restart. Redis 7 came out last week, and Functions are the feature I care about. Everything EVAL scripts should have been.&lt;/p&gt;
&lt;p&gt;The problem they solve is real. Take compare-and-update: set a value only if the stored version is older. From PHP it is three round trips and a race between them. WATCH/MULTI works but is ugly and retries under contention. A Lua script does it atomically in one trip, and that is why we all wrote Lua scripts. But scripts are anonymous blobs. The application carries the source, sends it on every deploy, and debugging starts with &amp;ldquo;which SHA is this&amp;rdquo;. Functions fix the operational part: the library lives in Redis, has a name, has a version.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Order status is an enum now</title>
      <link>https://useinov.com/posts/2022/enums-for-status/</link>
      <pubDate>Sat, 08 Jan 2022 23:15:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2022/enums-for-status/</guid>
      <description>&lt;p&gt;One row in &lt;code&gt;orders&lt;/code&gt; had status &lt;code&gt;&#39;canceled&#39;&lt;/code&gt;, with one l. Nobody knows how long it sat there. The enum found it on the first day.&lt;/p&gt;
&lt;p&gt;PHP 8.1 enums are six weeks old, enough time in one project to say something practical. The first candidate was obvious: order status. For years it was a class with string constants, &lt;code&gt;Order::STATUS_PAID&lt;/code&gt;, &lt;code&gt;Order::STATUS_SHIPPED&lt;/code&gt;. Constants look safe, but nothing stops a function from receiving &lt;code&gt;&#39;payed&#39;&lt;/code&gt;. The parameter type was &lt;code&gt;string&lt;/code&gt;, and &lt;code&gt;string&lt;/code&gt; accepts everything.&lt;/p&gt;</description>
    </item>
    <item>
      <title>PHP 8.1: enums, readonly and a word about Fibers</title>
      <link>https://useinov.com/posts/2021/php81-enums/</link>
      <pubDate>Fri, 26 Nov 2021 20:45:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2021/php81-enums/</guid>
      <description>&lt;p&gt;&lt;code&gt;&#39;payed&#39;&lt;/code&gt;. One letter, half a day of debugging, a few years ago. The string walked straight through &lt;code&gt;OrderStatus::isValid()&lt;/code&gt;, because someone had added it to the constants list &amp;ldquo;for compatibility&amp;rdquo;. Every project I touched has that class: a bag of string constants, an &lt;code&gt;isValid()&lt;/code&gt; helper, and a prayer. The type system never knew these strings were special.&lt;/p&gt;
&lt;p&gt;PHP 8.1 came out yesterday. Point release on paper, and it brings the feature I wanted since forever. Enums.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Nobody closes your database connection anymore</title>
      <link>https://useinov.com/posts/2021/long-lived-connections/</link>
      <pubDate>Tue, 20 Jul 2021 23:20:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2021/long-lived-connections/</guid>
      <description>&lt;p&gt;&amp;ldquo;MySQL server has gone away&amp;rdquo;, first thing in the morning, on a worker that had no traffic all night. That was week one after we moved a project from FPM to RoadRunner.&lt;/p&gt;
&lt;p&gt;For all these years FPM managed our database connections. Not with clever code. By dying. Process ends, connection closes, transaction rolls back, session variables reset. Nobody thought about connection lifecycle, because there was none.&lt;/p&gt;
&lt;p&gt;Move the same code to RoadRunner or Swoole workers and the topic is back. A connection lives for hours now. Two things go wrong, and both showed up within a week.&lt;/p&gt;</description>
    </item>
    <item>
      <title>The outbox table</title>
      <link>https://useinov.com/posts/2021/outbox-table/</link>
      <pubDate>Tue, 23 Mar 2021 15:30:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2021/outbox-table/</guid>
      <description>&lt;p&gt;About once a month an order existed and nobody heard about it. The order service saved the order, then published &lt;code&gt;order.created&lt;/code&gt; to RabbitMQ, and once a month the process died between the two. Rare enough to be mysterious. Frequent enough to ruin a weekend.&lt;/p&gt;
&lt;p&gt;Two operations, two systems, no common transaction. Whatever order you do them in, you lose. Commit first, then publish: the order exists, the event never leaves. Publish first, then commit: the commit fails, consumers are already busy with an order that does not exist. We had the first variant in production.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Attributes: metadata moves in with the code</title>
      <link>https://useinov.com/posts/2020/attributes-near-code/</link>
      <pubDate>Fri, 18 Dec 2020 12:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2020/attributes-near-code/</guid>
      <description>&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;#[&lt;/span&gt;&lt;span class=&#34;nd&#34;&gt;Route&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;/orders/{id}&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;methods&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;show&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nd&#34;&gt;Response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Symfony 5.2 accepts this on PHP 8, and the annotation era quietly starts to end. Same shape as the annotation, but now it is language, not a comment. The engine parses it, static analysis sees it, a typo is a compile-time complaint instead of a route that silently does not exist. Doctrine and the validator are heading the same way. Everything that lived in docblocks will move over the next year or two.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Types are for reading, not for the compiler</title>
      <link>https://useinov.com/posts/2020/typed-code-navigation/</link>
      <pubDate>Sat, 27 Jun 2020 22:20:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2020/typed-code-navigation/</guid>
      <description>&lt;p&gt;&lt;code&gt;OrderRepository&lt;/code&gt; in a constructor. Click. I am there. That is how I explore a big Symfony project now, and I noticed my main reason for types has changed. Not bug catching anymore. Navigation.&lt;/p&gt;
&lt;p&gt;Symfony 5.1 is out, PHP 7.4 is everywhere I work, and the IDE knows every caller and every implementation. Compare with the array-passing style we all wrote for years:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;register&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// what is in $data? read three call sites to find out
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;versus&lt;/p&gt;</description>
    </item>
    <item>
      <title>A Redis lock is a promise you cannot fully keep</title>
      <link>https://useinov.com/posts/2020/redis-lock-honesty/</link>
      <pubDate>Wed, 26 Feb 2020 21:45:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2020/redis-lock-honesty/</guid>
      <description>&lt;p&gt;Two workers, one order, processed twice. Every project gets this day. Someone writes &lt;code&gt;SETNX&lt;/code&gt;, calls it a distributed lock, closes the ticket. I want to slow down here, because the ticket is not closed.&lt;/p&gt;
&lt;p&gt;The small bugs first. A lock needs a TTL, or a crashed worker holds it forever. A lock needs an owner token, or worker A releases the lock of worker B:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;bin2hex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;random_bytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$ok&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$redis&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;lock:order:&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$orderId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;nx&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;ex&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;30&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the release must be atomic: compare the token and delete in one Lua script. Check in PHP, delete in a second command, and there is a gap. Something will land in that gap.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Cursor pagination is part of your API contract</title>
      <link>https://useinov.com/posts/2020/cursor-pagination-contract/</link>
      <pubDate>Wed, 08 Jan 2020 21:30:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2020/cursor-pagination-contract/</guid>
      <description>&lt;p&gt;Someone inserts a row while the client walks the pages, and the whole window shifts. Page two shows an item the client already saw on page one. Or an item falls between pages and the client never sees it. For a feed this is annoying. For an export or a sync endpoint this is a data loss bug that nobody can reproduce.&lt;/p&gt;
&lt;p&gt;I used to think keyset pagination is a performance trick. Deep OFFSET is slow, keyset is fast, end of story. Now I think the performance part is the boring half. Offset pagination over a changing dataset lies to the client. That is the interesting half.&lt;/p&gt;</description>
    </item>
    <item>
      <title>One use case, one class</title>
      <link>https://useinov.com/posts/2019/application-services/</link>
      <pubDate>Wed, 26 Jun 2019 15:30:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2019/application-services/</guid>
      <description>&lt;p&gt;Four hundred lines in a checkout action, half of them inside one try. That is one way a project dies. The other is &lt;code&gt;OrderManager&lt;/code&gt;: thirty methods, six injected services, and nobody can say what the class is for, because it is for everything about orders.&lt;/p&gt;
&lt;p&gt;Both diseases have the same cure and it is embarrassingly simple. One use case, one class, one public method.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;final&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;PlaceOrder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;__construct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;OrderRepository&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$orders&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;PaymentGateway&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$payments&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;EventDispatcher&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$events&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;...&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;handle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PlaceOrderCommand&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;OrderId&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// the whole story, top to bottom
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The shape answers three questions by itself. What comes in: one command object, a thing you can log, validate, put in a queue. What it needs: the constructor lists dependencies of this use case, not of orders in general, so when &lt;code&gt;PlaceOrder&lt;/code&gt; suddenly needs the mailer you see it and can ask why. What comes out: one result. Reading &lt;code&gt;handle()&lt;/code&gt; from top to bottom tells the whole story of placing an order. No chapter hidden in a base class or a trait.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Eloquent observers and hidden control flow</title>
      <link>https://useinov.com/posts/2019/eloquent-events-cost/</link>
      <pubDate>Wed, 27 Feb 2019 12:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2019/eloquent-events-cost/</guid>
      <description>&lt;p&gt;Three thousand welcome emails. A colleague ran a user import on one project last month: loop over a CSV, &lt;code&gt;$user-&amp;gt;save()&lt;/code&gt;, go home. Next morning we found the script had also warmed the search index three thousand times and invalidated cache after every row. Nobody wrote that in the import script. The observers did.&lt;/p&gt;
&lt;p&gt;Laravel 5.8 came out yesterday, and reading the changelog brought that evening back, so here is the note.&lt;/p&gt;</description>
    </item>
    <item>
      <title>One payment is enough</title>
      <link>https://useinov.com/posts/2019/laravel-job-idempotency/</link>
      <pubDate>Mon, 21 Jan 2019 20:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2019/laravel-job-idempotency/</guid>
      <description>&lt;p&gt;Charge the card, send the receipt, ack the job. The worker lost its Redis connection between step one and step three. The queue delivered the job again. The customer paid twice. Support learned some new words from him.&lt;/p&gt;
&lt;p&gt;Retries are not an edge case. Laravel retries failed jobs by design, and you want that, because networks blink. So every job with a side effect must answer one question: what happens if this runs twice. &amp;ldquo;It will not run twice&amp;rdquo; is not an answer. It will.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Where the checkout logic goes</title>
      <link>https://useinov.com/posts/2018/thin-controllers-laravel/</link>
      <pubDate>Mon, 13 Aug 2018 22:15:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2018/thin-controllers-laravel/</guid>
      <description>&lt;p&gt;Validate input, reserve stock, create the order, charge the card, fire events. Five steps in one checkout action, and the fat model versus fat controller argument offers only two rooms for them. Both rooms are wrong.&lt;/p&gt;
&lt;p&gt;Put it all in the controller and you cannot run checkout from anywhere except HTTP. No console command, no queue job, no test without the kernel. Put it in the Order model and the model now knows about payments, stock and notifications, a strange set of friends for an Eloquent class.&lt;/p&gt;</description>
    </item>
    <item>
      <title>GDPR: where the user actually lives</title>
      <link>https://useinov.com/posts/2018/gdpr-data-map/</link>
      <pubDate>Wed, 09 May 2018 10:45:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2018/gdpr-data-map/</guid>
      <description>&lt;p&gt;The row in &lt;code&gt;users&lt;/code&gt;. Then orders, with the delivery address and phone. Then nginx access logs with emails inside GET parameters of an old unsubscribe endpoint. That is how far I got in the first ten minutes of drawing where one user&amp;rsquo;s data lives on one project, and the map did not fit on one page.&lt;/p&gt;
&lt;p&gt;May 25 is close and everyone discusses consent banners. The lawyers can have the banners. The engineering part is the right to erasure. &amp;ldquo;Delete the user&amp;rdquo; sounds like one DELETE statement until you sit down with a pen.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Private services: the container steps back</title>
      <link>https://useinov.com/posts/2017/private-services/</link>
      <pubDate>Tue, 30 May 2017 15:15:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2017/private-services/</guid>
      <description>&lt;p&gt;&lt;code&gt;$container-&amp;gt;get(&#39;app.mailer&#39;)&lt;/code&gt; stopped working this week. Symfony 3.3 is out, and in the new configuration services are private by default. The container refuses the call. Everyone is writing about autowiring. This is the change I like more.&lt;/p&gt;
&lt;p&gt;That call was always a smell. A class that pulls dependencies from the container by string id is a class with secrets. Its constructor says nothing, and to test it you boot half the framework, because it may ask for anything at any moment.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Order status is a graph</title>
      <link>https://useinov.com/posts/2016/order-status-graph/</link>
      <pubDate>Wed, 30 Nov 2016 22:10:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2016/order-status-graph/</guid>
      <description>&lt;p&gt;&lt;code&gt;if ($order-&amp;gt;status == &#39;paid&#39;)&lt;/code&gt; in the shipping module. Another one in the refund module. A slightly different one in the admin panel. Then someone cancels a shipped order from an admin button, because the button just writes the string, and the warehouse learns about it a week later.&lt;/p&gt;
&lt;p&gt;Symfony 3.2 is out this week and ships a Workflow component. Finally a framework says out loud what every order table has been whispering for years: status is a state machine, not a string column you assign wherever convenient.&lt;/p&gt;</description>
    </item>
    <item>
      <title>The queue will run your job twice</title>
      <link>https://useinov.com/posts/2016/idempotent-jobs/</link>
      <pubDate>Mon, 03 Oct 2016 15:20:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2016/idempotent-jobs/</guid>
      <description>&lt;p&gt;The job called the payment provider. The call succeeded. Then the worker timed out before it marked the job done. The queue did what queues do: it retried. The provider did what it was asked: it charged again. The customer did what customers do and wrote an angry email.&lt;/p&gt;
&lt;p&gt;Nobody made a mistake here. The queue promises at-least-once delivery, and &amp;ldquo;at least&amp;rdquo; is written in the contract. Network partitions, worker crashes, deploy restarts. Sooner or later every job runs twice, and the jobs that hurt are exactly the ones with external effects: payments, emails, webhooks, API calls.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Laravel 5.3: the app is not a website anymore</title>
      <link>https://useinov.com/posts/2016/laravel-53-notifications/</link>
      <pubDate>Fri, 26 Aug 2016 12:30:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2016/laravel-53-notifications/</guid>
      <description>&lt;p&gt;&lt;code&gt;Mail::send()&lt;/code&gt; inside the checkout controller. A second copy inside the API controller. Then someone adds the chat webhook to one copy and forgets the other. Every project has this code. I have written it more than once.&lt;/p&gt;
&lt;p&gt;Laravel 5.3 came out this week, and the two big pieces, Notifications and Passport, both point the same way. The application is no longer a thing that renders HTML. It is a core that talks to browsers, mobile clients and third parties, and HTML is one of the outputs.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Freezing the Kohana layer</title>
      <link>https://useinov.com/posts/2016/freezing-kohana/</link>
      <pubDate>Fri, 04 Mar 2016 20:10:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2016/freezing-kohana/</guid>
      <description>&lt;p&gt;Kohana 3.3, on a project that earns money every day. The framework is effectively finished. The repository barely moves, the community left years ago. Nobody will approve a rewrite, and I have stopped asking. This is a normal situation and it deserves a better plan than &amp;ldquo;someday we migrate&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The plan we settled on is a freeze.&lt;/p&gt;
&lt;p&gt;Pin the exact framework version and vendor it. Not &amp;ldquo;3.3.*&amp;rdquo;, the exact commit. The build must be reproducible in five years, when the original download link is dead.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony 3.0 is not a rewrite</title>
      <link>https://useinov.com/posts/2015/symfony-two-step/</link>
      <pubDate>Mon, 30 Nov 2015 22:05:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2015/symfony-two-step/</guid>
      <description>&lt;p&gt;Symfony 2.8 and 3.0, released today, both at once, on purpose. The pairing is the whole message. 2.8 is the last of the 2.x line and an LTS. 3.0 is roughly 2.8 with the deprecated code deleted. Same features, cleaner body.&lt;/p&gt;
&lt;p&gt;This turns a scary major upgrade into two steps.&lt;/p&gt;
&lt;p&gt;Step one, move to 2.8. Minor upgrade, low risk. Then make the deprecation log empty. The phpunit bridge prints every deprecated call your code and tests touch. Burn the list down during normal sprints, item by item, each fix small and releasable on its own.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Redis cache and Redis queue are different databases</title>
      <link>https://useinov.com/posts/2015/redis-two-jobs/</link>
      <pubDate>Fri, 21 Aug 2015 19:45:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2015/redis-two-jobs/</guid>
      <description>&lt;p&gt;Two lines from redis.conf on one project:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;maxmemory 2gb
maxmemory-policy allkeys-lru
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Correct for a cache. Then sessions moved into the same instance, because Redis was already there. Then the job queue, same reason. One process, three tenants. This works until the day it does not.&lt;/p&gt;
&lt;p&gt;Cache data is disposable by definition, and those two lines embrace that. Memory fills up, Redis evicts the coldest keys, the application rebuilds them on demand. Persistence is optional. After a restart a cold cache is an inconvenience, not an incident.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Laravel 5.1 and the dependency ladder</title>
      <link>https://useinov.com/posts/2015/dependency-ladder/</link>
      <pubDate>Sun, 14 Jun 2015 19:10:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2015/dependency-ladder/</guid>
      <description>&lt;p&gt;PHP 5.5.9. That is the minimum for Laravel 5.1, which came out this week, the first Laravel with LTS: two years of bug fixes, three of security fixes. Two weeks ago I wrote the same about Symfony 2.7. Two frameworks arriving at the same idea in one summer is no coincidence. PHP applications got old enough to have a lifecycle.&lt;/p&gt;
&lt;p&gt;Draw the ladder for your project once. OS at the bottom. Then the PHP version the OS ships, or the one you build. Then the framework. Then the packages on top. Every rung has its own end-of-life, and the rungs are coupled. If production sits on some old distro with 5.4, the 5.1 upgrade is suddenly an ops project. And above you, half the packages will require 5.1 in their next major, so staying behind slowly cuts you off from fixes.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony 2.7: LTS is the feature</title>
      <link>https://useinov.com/posts/2015/symfony-lts/</link>
      <pubDate>Sun, 31 May 2015 12:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2015/symfony-lts/</guid>
      <description>&lt;p&gt;Three years of bug fixes. Four years of security fixes. Symfony 2.7 came out this weekend, and that is the line from the announcement I care about. The changelog has new things in it, but the important word is on the label: LTS.&lt;/p&gt;
&lt;p&gt;For a pet project this is boring. For a product with paying users and a team, this is the actual feature.&lt;/p&gt;
&lt;p&gt;A big application does not upgrade for fun. Every framework upgrade is testing time, regression risk, and a sprint that produces nothing visible. Business asks a fair question: what do we get. &amp;ldquo;Newer version&amp;rdquo; is not an answer. &amp;ldquo;We keep receiving security fixes until 2018 without touching anything&amp;rdquo; is.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Three ways to get a dependency in Laravel</title>
      <link>https://useinov.com/posts/2015/container-or-facade/</link>
      <pubDate>Mon, 09 Mar 2015 21:10:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2015/container-or-facade/</guid>
      <description>&lt;p&gt;Same repository, three ways to get it in Laravel 5.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 1. Constructor injection
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;__construct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;OrderRepository&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$orders&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$this&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;orders&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$orders&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 2. Facade
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$order&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Orders&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;find&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 3. Service locator
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$orders&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;App&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;make&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;App\Repositories\OrderRepository&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All three work. The container resolves everything either way. The difference is in what the class tells you about itself.&lt;/p&gt;
&lt;p&gt;With constructor injection the dependencies are in the signature. You open the class, you read the constructor, you know what it needs. A test passes a mock and never touches the container.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Composer in a legacy Kohana project</title>
      <link>https://useinov.com/posts/2015/kohana-meets-composer/</link>
      <pubDate>Thu, 22 Jan 2015 20:05:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2015/kohana-meets-composer/</guid>
      <description>&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;require&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;APPPATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;../vendor/autoload.php&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One line in &lt;code&gt;bootstrap.php&lt;/code&gt;. This month it became the whole strategy for a project we decided to keep on Kohana.&lt;/p&gt;
&lt;p&gt;The framework is quiet, the project is alive. These two facts have to coexist somehow. The plan: nothing new gets written the Kohana way. New dependencies come through Composer, and Kohana&amp;rsquo;s autoloader and Composer&amp;rsquo;s autoloader live together fine.&lt;/p&gt;
&lt;p&gt;First candidate was the HTTP client. We talk to two external APIs, and the code around &lt;code&gt;Request_Client_External&lt;/code&gt; was not pretty. Guzzle is better in every way. But Guzzle does not spread through the codebase. There is an interface:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony best practices: defaults, not laws</title>
      <link>https://useinov.com/posts/2014/symfony-best-practices/</link>
      <pubDate>Sun, 14 Dec 2014 20:30:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2014/symfony-best-practices/</guid>
      <description>&lt;p&gt;One AppBundle. That is the line in the official Symfony Best Practices book I read twice. For years the answer to &amp;ldquo;how many bundles&amp;rdquo; was &amp;ldquo;as many as you have features&amp;rdquo;, and here the framework&amp;rsquo;s own book says: one.&lt;/p&gt;
&lt;p&gt;The book came out this autumn. Worth reading even if you are not on Symfony, because the interesting part is the change of tone.&lt;/p&gt;
&lt;p&gt;The old Symfony way: everything is a bundle. Your application is bundles, reusable, configurable, with their own extensions and semantic configuration. Very flexible. And for a normal business application, mostly ceremony. You write a configuration class for code that will never leave this one project. I wrote such classes. Three of them. None was ever reused.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Services instead of fat controllers</title>
      <link>https://useinov.com/posts/2014/services-instead-of-fat-controllers/</link>
      <pubDate>Tue, 02 Dec 2014 22:20:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2014/services-instead-of-fat-controllers/</guid>
      <description>&lt;p&gt;120 lines in one action. Order confirmation: load the order, charge the card, send the email, render the page. It ran fine for a year. Then a console script needed the same operation, and the action turned out to be glued to SMTP, to the database and to the HTTP request. All at once.&lt;/p&gt;
&lt;p&gt;Symfony people talk about dependency injection so much it sounds like religion. It is one simple idea: a class receives its dependencies and does not create them.&lt;/p&gt;</description>
    </item>
    <item>
      <title>HMVC in Kohana: a request inside a request</title>
      <link>https://useinov.com/posts/2014/kohana-hmvc/</link>
      <pubDate>Mon, 10 Nov 2014 21:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2014/kohana-hmvc/</guid>
      <description>&lt;p&gt;&lt;code&gt;Request::factory(&#39;widgets/stats&#39;)&lt;/code&gt; inside a controller. A colleague looked at this line last week and asked the question I hear most often about Kohana: you already have a request, why make another one?&lt;/p&gt;
&lt;p&gt;I keep answering it at the desk. So I will write it down once. Short notes, mostly backend, mostly PHP. This is the first.&lt;/p&gt;
&lt;p&gt;The answer is composition. Take a dashboard. A stats block, a recent orders block, a notifications block. Each one needs its own data and its own logic. You can put all of it into one action. It works. In a month it is 300 lines and nobody wants to open it.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
