<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Omer&apos;s playground</title><description>Hands-on notes from an engineering leader on Kubernetes and home labs, self-hosting, Rust, TypeScript and building software with AI coding agents.</description><link>https://omeratagun.net/</link><language>en-us</language><item><title>Moving 20,000 workspaces between microservices with no incidents</title><link>https://omeratagun.net/blog/migration-in-complex-microservices/</link><guid isPermaLink="true">https://omeratagun.net/blog/migration-in-complex-microservices/</guid><description>How we moved bookmarks for 20,000 workspaces into the services that own them, across six teams: no API break, no downtime, no incidents, no data loss.</description><pubDate>Sun, 18 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This project didn’t start with writing code. It started with two to three months of reading other people’s: drawing lines between services, and finding every place where one thing quietly depended on another.&lt;/p&gt;
&lt;p&gt;By the time we pitched it to the executives we had an estimated deadline, t-shirt sized efforts per team, and a clear list of what the project needed to succeed. That list mattered more than the estimate, and the first thing on it was people. This was not a cross-team effort, it was a cross-tribe one. More than six teams were involved: answering questions, sharing knowledge, making small adjustments and reviewing changes. The actual work, including changes in other teams’ code, was done by one small dedicated team.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A migration is not a data problem. It’s an ownership problem that happens to involve data.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the story of how we moved bookmarks home.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;what-we-were-moving&quot;&gt;What we were moving&lt;/h2&gt;
&lt;p&gt;The entity was what we call a &lt;strong&gt;bookmark&lt;/strong&gt;. It lived in a service we didn’t own. A bookmark is not a flat row. It has a &lt;code&gt;type&lt;/code&gt; property, &lt;code&gt;diagram&lt;/code&gt; or &lt;code&gt;report&lt;/code&gt;, that says what kind of bookmark it is, and child entities for the diagram or report itself. Those child entities have &lt;strong&gt;working copies&lt;/strong&gt;, which are another layer of the problem on their own.&lt;/p&gt;
&lt;p&gt;So “move the bookmarks” really meant: move a tree of entities, with drafts hanging off its branches, without losing a single relationship. And not all into one place. Diagrams had to go to the diagrams service, reports to the reports service.&lt;/p&gt;
&lt;p&gt;The scale: around &lt;strong&gt;20,000 workspaces&lt;/strong&gt; across &lt;strong&gt;8 regions&lt;/strong&gt;, &lt;strong&gt;millions of diagrams&lt;/strong&gt; and gigabytes of data.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;why-move-it-at-all&quot;&gt;Why move it at all&lt;/h2&gt;
&lt;p&gt;Bookmarks could have lived in that service just fine. Technically, nothing was broken.&lt;/p&gt;
&lt;p&gt;Organizationally, it was a bottleneck. When the data your service is supposed to own lives somewhere else, every change turns into overhead: meetings, requests to another team, waiting on their roadmap, and maintenance on code that isn’t yours. None of that shows up in an architecture diagram, but all of it shows up in how fast you ship.&lt;/p&gt;
&lt;p&gt;We wanted the data to live with the team that owns the domain. That was the whole reason, and it was a good enough one to justify everything that came after.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;a-tiger-team&quot;&gt;A tiger team&lt;/h2&gt;
&lt;p&gt;What I asked the executives for was a &lt;strong&gt;team of four&lt;/strong&gt;. Five with me.&lt;/p&gt;
&lt;p&gt;The reason for four was simple. Four people split into &lt;strong&gt;two micro teams&lt;/strong&gt; that can work on different parts of the problem in parallel. And once the pieces of the puzzle start coming together, the knowledge of both halves combines into one picture. That combined knowledge is what gets you over the line.&lt;/p&gt;
&lt;p&gt;We didn’t borrow time from people inside their existing teams. We created a separate team, mixed from the teams within our tribe, with one job: this migration. I led it end to end, both writing code with them and managing the project.&lt;/p&gt;
&lt;p&gt;That setup turned out to be one of the best decisions of the whole thing. A small team with a single goal moves fast. No context switching between sprint goals, no “I’ll get to it after my other tickets”, and every decision a conversation between five people instead of a meeting across teams.&lt;/p&gt;
&lt;p&gt;The people in it were surprised by how efficient we were. They were also happy to step out of their usual team routines for a while and work on something new and seriously complex. The feedback afterwards was great.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;the-rules&quot;&gt;The rules&lt;/h2&gt;
&lt;p&gt;Three constraints shaped every decision:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;No breaking API.&lt;/strong&gt; We could not tell customers “we moved things, please use this new API from now on”. Every existing call had to keep working.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No downtime.&lt;/strong&gt; The product had to stay up the whole time. Reads never stopped. The only thing a customer could notice was a short pause on changes to their own workspace while it was being moved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No access to the old database.&lt;/strong&gt; For many reasons, among them security and the complexity of giving two different namespaces access to the same database, we could not just read the old tables directly. Everything had to go through the owning service.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each of these alone is manageable. All three together is what made it open heart surgery.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-1-bookmark-proxy&quot;&gt;Chapter 1: bookmark-proxy&lt;/h2&gt;
&lt;p&gt;Rule 1 meant callers could not know or care where a bookmark lived. So we put something in front of all the services: &lt;strong&gt;bookmark-proxy&lt;/strong&gt;, a gateway that takes incoming requests and hands them over to the right upstream service.&lt;/p&gt;
&lt;p&gt;It answers two questions for every request:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Is this workspace migrated yet?&lt;/strong&gt; If not, the old service is still the source of truth.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What type of bookmark is this?&lt;/strong&gt; If the workspace is migrated, the &lt;code&gt;type&lt;/code&gt; property decides who owns it: diagrams go to the diagrams service, reports to the reports service.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first question is why the proxy also keeps the &lt;strong&gt;migration state&lt;/strong&gt; per workspace. Without it, there is no way to know where to route.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;┌────────┐     ┌────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│ client │ ──▶ │ bookmark-proxy │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└────────┘     └───────┬────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                       │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             workspace migrated?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                       │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ┌──── no ────┴──── yes ────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ▼                          │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ┌─────────────┐             bookmark type?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   │ old service │                   │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   └─────────────┘         ┌ diagram ┴ report ┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                           ▼                  ▼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                ┌──────────────────┐ ┌─────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                │ diagrams service │ │ reports service │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                └──────────────────┘ └─────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the state is kept per workspace, a workspace is either fully on the old side or fully on the new one, never half and half. The same state also handles the two moments that make people nervous:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Writes during the migration.&lt;/strong&gt; While a workspace is being migrated, bookmark-proxy blocks mutations to it and returns an error. Reads keep working, so people can keep doing their work. Because the migration runs per workspace, that window is small, and the data we read can’t change underneath us.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rollback.&lt;/strong&gt; If something goes wrong, we flip the workspace’s state in bookmark-proxy back to the old service. Traffic goes back to where it came from, without moving any data.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-2-one-step-at-a-time&quot;&gt;Chapter 2: one step at a time&lt;/h2&gt;
&lt;p&gt;We had two options for the rollout: migrate per workspace or per region. We did both, in order of risk.&lt;/p&gt;
&lt;p&gt;First came &lt;strong&gt;dry runs&lt;/strong&gt;, to see the migration run end to end without committing to anything. Then a couple of &lt;strong&gt;standalone workspaces of our own&lt;/strong&gt;, so that if something went wrong, the only customer affected was us. After that we went &lt;strong&gt;region by region&lt;/strong&gt;, starting with the smallest regions and working up to the biggest ones. Every step had to go cleanly before we took the next one.&lt;/p&gt;
&lt;p&gt;The whole rollout took &lt;strong&gt;4 hours&lt;/strong&gt; in total. The migration itself didn’t need downtime, but we still ran it inside our separate weekly downtime hour. Not because we had to, but so that enough people were around to support if things didn’t go well.&lt;/p&gt;
&lt;p&gt;The same thinking decided the order of the domains. &lt;strong&gt;Diagrams went first&lt;/strong&gt;, into the existing diagrams service. It was the smaller step, and it told us whether the whole approach worked: the proxy, the streaming, the events, the snapshots, all of it on real data.&lt;/p&gt;
&lt;p&gt;Only once diagrams had succeeded did we move on to &lt;strong&gt;reports&lt;/strong&gt;. That was the bigger step, because the reports domain didn’t exist yet. It had to be built from the ground up.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-3-full-api-parity&quot;&gt;Chapter 3: full API parity&lt;/h2&gt;
&lt;p&gt;The original service was written in Java and Kotlin. Both of ours, diagrams and reports, are NestJS. Rule 1 meant we needed full API parity, not “close enough”. For reports, that meant a whole new domain that had to behave exactly like the old one from the first request.&lt;/p&gt;
&lt;p&gt;Parity is never a straight port. Some features had to go. Some needed enhancements while we were at it. Every one of those decisions was a conversation, because someone, somewhere, depended on the old behaviour.&lt;/p&gt;
&lt;p&gt;And parity is not only about the shape of the responses, it is about how they hold up under load. Miss an index and you find out quickly when hundreds of requests per second start hitting the endpoint. Same response, very different day.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-4-events&quot;&gt;Chapter 4: events&lt;/h2&gt;
&lt;p&gt;Bookmarks were not just rows in a microservice. Part of their state, not all of it, was also distributed to other services through &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/event-hubs/&quot;&gt;Azure Event Hubs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So our services had to start producing those events, and consumers had to be adjusted as part of the project. The catch: a producer can only be shipped by one service. We could not just produce from our services into the same producer the old service was using.&lt;/p&gt;
&lt;p&gt;So we added a &lt;strong&gt;new event hub&lt;/strong&gt; for the new owners to produce into, and changed the consumers’ code to read from &lt;strong&gt;both&lt;/strong&gt; event hubs, using the bookmark type to tell which one to listen to for what. That kept every consumer correct whether a workspace had moved yet or not.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-5-moving-the-data&quot;&gt;Chapter 5: moving the data&lt;/h2&gt;
&lt;p&gt;With no database access, the data had to come through the old service. We &lt;strong&gt;streamed&lt;/strong&gt; it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Read in a transaction.&lt;/strong&gt; We read the data transactionally, so every workspace was exported from one consistent point in time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Streamed, not loaded.&lt;/strong&gt; Nothing was held in memory as a whole, so there was no heap instability, no matter how large a workspace was.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In foreign key order.&lt;/strong&gt; Parents before children, children before working copies. That way every row could be written as it arrived, and snapshots taken on the new side could capture the data correctly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last point turned out to be more important than it looked, because of the hardest part of the whole project.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-6-snapshots-the-hardest-part-of-all&quot;&gt;Chapter 6: snapshots, the hardest part of all&lt;/h2&gt;
&lt;p&gt;Workspaces have snapshots. A snapshot-manager orchestrates them across all upstream services, including ours.&lt;/p&gt;
&lt;p&gt;Here is where it got complicated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Templates.&lt;/strong&gt; New workspaces can start from templates with default data. Part of that data lived in the snapshot-manager, part of it in the old service’s &lt;code&gt;.sql&lt;/code&gt; dump.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retention.&lt;/strong&gt; Snapshots are kept for &lt;strong&gt;90 days&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So migrating a workspace today raises an uncomfortable question: for the next 90 days, whose snapshot is the right one? A snapshot taken before the migration came from the old service. Restoring it would bring back data in the old service’s world, not ours.&lt;/p&gt;
&lt;p&gt;We answered it with a &lt;strong&gt;remigration worker&lt;/strong&gt;. If a workspace was restored from a snapshot that was not taken from our services, the worker remigrates that workspace using our streaming maintenance endpoint, the same streaming path we used for the original migration. The data is pulled through again, in foreign key order, and ends up where it belongs.&lt;/p&gt;
&lt;p&gt;It is not glamorous. It is the kind of edge case that only shows up when you ask “and what happens in 89 days?”. Most of the work in this project was asking that question, over and over, about every corner.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;how-it-ended&quot;&gt;How it ended&lt;/h2&gt;
&lt;p&gt;20,000 workspaces, 8 regions, 4 hours. No incidents. No data loss.&lt;/p&gt;
&lt;p&gt;I am super proud of that. It took nerves, tons of alignment across teams and tribes, a lot of edge cases covered across many services, and a five-person tiger team that carried it all the way. From the outside, it looked like nothing happened.&lt;/p&gt;
&lt;p&gt;That’s the goal. When a migration goes well, nobody notices.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;what-id-do-the-same-way-again&quot;&gt;What I’d do the same way again&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Spend the time on discovery up front.&lt;/strong&gt; The months of reading code and mapping connections are why there were no surprises later. Every surprise in a migration like this is an incident.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pitch the needs, not just the estimate.&lt;/strong&gt; For us the biggest need was a dedicated team, not a slice of everyone’s time. A small tiger team doing the work, with other teams sharing knowledge and reviewing, beats six teams each giving a little.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Start small, and keep a way back.&lt;/strong&gt; Dry runs, our own workspaces, then regions from smallest to biggest. Diagrams before reports. And at every step, rollback was one state flip away.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parity includes performance.&lt;/strong&gt; Same responses at hundreds of requests per second, or it isn’t parity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stream, in order.&lt;/strong&gt; Transactional reads, foreign key order, no heap. It is what made the migration and the remigration the same path.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ask what happens in 90 days.&lt;/strong&gt; Anything with history, like snapshots, outlives the migration day. Plan for it before it surprises you.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Till the next one. Stay hydrated.&lt;/p&gt;</content:encoded><category>blog</category><category>microservices</category><category>migration</category><category>zero downtime</category><category>nestjs</category><category>architecture</category><category>event streaming</category><category>azure event hubs</category><category>engineering leadership</category><category>tiger team</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Sigilreach: nine days, a thousand commits and a medieval world that lives without me</title><link>https://omeratagun.net/blog/sigilreach-medieval-world-with-agents/</link><guid isPermaLink="true">https://omeratagun.net/blog/sigilreach-medieval-world-with-agents/</guid><description>I started a realistic medieval survival and settlement game in Unity with a handful of AI coding agents. Nine days and 1,064 commits later, here is what the stack looks like, why URP and not HDRP, how the asset hunt went, and what it took to keep tests fast and the game from freezing.</description><pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have wanted to build a game for a long time. Not a weekend jam, a real one. Bellwright, Manor Lords and Kingdom Come scratched the itch, but every time I played them I ended up thinking about the same thing: the villagers are props. They stand where the designer put them. The village is there because someone placed it.&lt;/p&gt;
&lt;p&gt;So on the 2nd of October I opened Unity and wrote a vision doc with one line at the top:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A medieval frontier built by its people: every villager wants something, the map changes because of what they do, and it goes on with or without you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s &lt;strong&gt;Sigilreach&lt;/strong&gt;. Nine days later the repository has &lt;strong&gt;1,064 commits&lt;/strong&gt;, about 45,000 lines of pure C# game rules, 2,500 domain tests, a 6 by 6 kilometre generated land, a castle, a smithy, marriages, murders, outlaw hunts and villagers who think with a local language model.&lt;/p&gt;
&lt;p&gt;I didn’t write most of that code by hand. A small crew of Claude sessions did, with me as the owner, the reviewer and the person who plays the build at night and comes back with a list. This post is about what that looked like, what broke, and what I would do the same way again.&lt;/p&gt;
&lt;p&gt;Wear your helmets.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;what-the-game-is&quot;&gt;What the game is&lt;/h2&gt;
&lt;p&gt;Single-player, realistic, no stylisation, no magic. You wake up as a stranger near a lord’s stone castle with nothing in your pockets. Everyone else is a simulated person with traits, needs, a dream (to be rich, to lead the town, a home of their own, to answer to no one), memories and opinions of each other.&lt;/p&gt;
&lt;p&gt;The pillars that shaped every technical decision:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NPCs make the world.&lt;/strong&gt; Hamlets get founded because someone wanted to get away from a leader they hate. Villages split, feud, steal, raid and die.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Same rules for everyone.&lt;/strong&gt; The player is an &lt;code&gt;Actor&lt;/code&gt;. So is every villager. Gathering, crafting, building and fighting are the same code paths for both. If a villager can’t do it, the feature isn’t done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No static villages.&lt;/strong&gt; Nobody lays out a village. Streets are worn by walking, homes line them, buildings go up piece by piece with scaffolding and hauled materials.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progress is material.&lt;/strong&gt; No XP trees. Tools gate resources, stations gate recipes, people gate the top tiers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That second pillar turned out to be the most important architecture decision in the whole project, and I’ll come back to it.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;the-stack&quot;&gt;The stack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unity 6.3 LTS&lt;/strong&gt; with the &lt;strong&gt;Universal Render Pipeline&lt;/strong&gt;, Deferred+ renderer on PC.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A pure C# domain&lt;/strong&gt; (&lt;code&gt;Sigilreach.Domain&lt;/code&gt;) with &lt;code&gt;noEngineReferences&lt;/code&gt;. Every rule lives there: needs, combat, construction, crafting, society, the land itself. No &lt;code&gt;UnityEngine&lt;/code&gt; anywhere.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime&lt;/strong&gt; is thin MonoBehaviour glue: bodies, input, UI, terrain streaming.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Editor generators&lt;/strong&gt; build every scene, prefab and icon from code. Nobody hand-edits a scene. If something looks wrong you change the generator and rebuild.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content as CSV&lt;/strong&gt; with stable ids. Items, recipes, nodes, building pieces, blueprints, creatures, lessons. Code asks for components and tags, never for a specific id.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A local LLM&lt;/strong&gt; for the villagers’ minds: LLM for Unity (llama.cpp) with Qwen3-4B in 4-bit GGUF, about 2.5 GB, shipped inside every build and on by default.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-the-domain-is-engine-free&quot;&gt;Why the domain is engine-free&lt;/h3&gt;
&lt;p&gt;This is the decision everything else rests on. Because the rules don’t know Unity exists, they compile with plain &lt;code&gt;dotnet&lt;/code&gt; as &lt;code&gt;netstandard2.1&lt;/code&gt; with C# 9, the same API level and language version Unity uses. Which means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tests run in seconds without the editor;&lt;/li&gt;
&lt;li&gt;they run &lt;strong&gt;in the cloud&lt;/strong&gt;, on agents that will never see a Unity window;&lt;/li&gt;
&lt;li&gt;they run while I’m in Play mode or the editor is busy rebuilding a 6 km world;&lt;/li&gt;
&lt;li&gt;a whole village can be simulated headless for 30 in-game days and print a chronicle of who married whom, who stole, who froze to death and why.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last point is how you debug emergent behaviour. You don’t. You read the chronicle.&lt;/p&gt;
&lt;h3 id=&quot;why-urp-and-not-hdrp&quot;&gt;Why URP and not HDRP&lt;/h3&gt;
&lt;p&gt;People ask this every time I show a screenshot, so here it is. The decision is in the vision doc, dated day one:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;URP over HDRP because it has wider asset-store support and is where Unity’s renderer investment is going; the realistic look comes from assets and lighting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In practice that played out like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The asset store is a URP world.&lt;/strong&gt; Every pack I bought ships URP materials or a URP package for Unity 6.3: NatureManufacture’s Meadow and Forest, The Tales Factory’s characters, the castle kit, GameAssetFactory’s farm buildings, MicroSplat’s terrain shader. One character pack I passed on was HDRP-first, and that was one of the reasons.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The realistic look comes from assets and light, not the pipeline.&lt;/strong&gt; MicroSplat for the terrain with anti-tiling, NatureManufacture’s water, a proper grade with ACES and bloom, volumetric clouds, a time-of-day light that changes colour through the day.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What we gave up&lt;/strong&gt;, honestly: Unity’s Physically Based Sky and volumetric clouds are HDRP-only. URP 6.3 has no screen-space reflections, and planar reflections are too expensive on a 6 km land, so water reflects the sky through environment reflection. For clouds we vendored an MIT-licensed URP volumetric clouds and sky project instead of paying for one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are two more reasons that I believe were in play but that the project never wrote down, so take them as my reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The world is huge and the frame budget is shared.&lt;/strong&gt; A 6 km streamed land, hundreds of simulated people, a language model on the same GPU. URP’s lighter per-frame cost leaves room for the simulation and the mind.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The main dev machine is a Mac.&lt;/strong&gt; URP is the comfortable path on Apple silicon, and the builds I play are macOS builds.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;the-asset-hunt&quot;&gt;The asset hunt&lt;/h2&gt;
&lt;p&gt;There is no art team. There is me, the Unity Asset Store and a budget I kept trying to stay under. The repo has an &lt;code&gt;ASSETS.md&lt;/code&gt; with every pack, what it was bought for, what it cost and what was rejected and why. Total so far is about &lt;strong&gt;$609&lt;/strong&gt; for everything installed, with about &lt;strong&gt;$838&lt;/strong&gt; estimated to a vertical slice, down from the first estimate of $1,100 to $1,250.&lt;/p&gt;
&lt;p&gt;The buying rules we settled on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Buy for the current milestone.&lt;/strong&gt; Exception: a real sale on something we’re sure to need.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test-import before relying on a pack.&lt;/strong&gt; Under our URP lighting, check Unity 6.3 compatibility, polygon counts, LODs and material count.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One visual family per category.&lt;/strong&gt; A subset of one pack beats a mix of three styles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Import only the sub-packs you use.&lt;/strong&gt; Bundles unlock access; each pack downloads separately and some are many gigabytes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The rejections taught me more than the purchases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A modular house pack at $99.99 turned out to be about a &lt;strong&gt;million triangles per hut&lt;/strong&gt;. I bought GameAssetFactory’s Medieval Farm Modular for $39.99 instead: 3 m grid, sane triangle counts, real thatch and timber framing. That one decision is why villagers can build houses piece by piece today.&lt;/li&gt;
&lt;li&gt;A castle pack had 100K-triangle walls and gothic Roman ruins. Another had 2M-triangle tents. Another was a French château. The castle we use was &lt;strong&gt;€22.77 on a 55% sale&lt;/strong&gt; instead of a $120 alternative.&lt;/li&gt;
&lt;li&gt;A character pack at €202 had fixed variants at 59 to 93K triangles each and no soldiers. The Tales Factory’s modular characters map straight onto our clothing slots, so a crafted tunic shows up as a tunic.&lt;/li&gt;
&lt;li&gt;A grain crops pack got rejected for being mobile quality.&lt;/li&gt;
&lt;li&gt;Deer and domestic animal packs at €184 and €165 got dropped when PROTOFACTOR’s full animal pack showed up at &lt;strong&gt;€69 on a 50% sale&lt;/strong&gt; with both.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the gotchas nobody warns you about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One character pack’s URP sub-package re-assigns a material’s GUID and leaves it on the Standard shader. Pink head. You fix it by hand once and write it down.&lt;/li&gt;
&lt;li&gt;The castle pack’s particle materials use the built-in particle shader, magenta in URP. Its bonfire now runs on our own URP particle materials made from its textures.&lt;/li&gt;
&lt;li&gt;The castle pack also had a custom grunge shader Unity 6.3’s URP can’t compile. Moved to URP Lit.&lt;/li&gt;
&lt;li&gt;The women’s skirts are open tubes of single-sided cloth. When a stride shears the skinned mesh at a distance, the inside turns to the camera, isn’t drawn, and you get a woman with no legs below the knee.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two rules came out of all this that I’m happy with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Store packs never go into git.&lt;/strong&gt; They’re gitignored, and &lt;code&gt;ASSETS.md&lt;/code&gt; has the exact import steps per pack. A fresh clone plus the packs plus one menu click rebuilds the whole game.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;There is no fallback art.&lt;/strong&gt; Early on we had a “Greybox” art set so the game ran without packs. It doubled every generator and every test. On day six I deleted it entirely. Nobody should run this game without its art.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On day four I also changed my mind on AI-generated art. It’s allowed now, judged on quality like anything else, and disclosed on Steam at release. The first piece is the UI kit.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;a-world-nobody-laid-out&quot;&gt;A world nobody laid out&lt;/h2&gt;
&lt;p&gt;The land is generated from one fixed seed: 6 by 6 kilometres, the same land every game, populated differently every time.&lt;/p&gt;
&lt;p&gt;The first version laid rivers first and carved the land around them. Every valley floor sat at 0.5 m and the water was one flat sheet, so nothing could flow downhill and the whole 6 km read as a plain with 40 m of relief. We turned it around:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Heights first&lt;/strong&gt; on an 8 m grid: mountain ranges along the edges, an inland massif, warped hill country, lowland basins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Erosion on the grid:&lt;/strong&gt; hydraulic droplets cut gullies and leave soft floors, then thermal erosion settles slopes into scree.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rivers from flow:&lt;/strong&gt; depressions fill into lakes, flow gathers downhill, the biggest channels become rivers, fords sit where the channel is wide and shallow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of that lives in the domain, so the simulation reads the same heights the terrain shows. Villagers pick where to settle and where to cross a river from the same &lt;code&gt;Land&lt;/code&gt; model the terrain builder uses. No hand-finishing with terrain tools, ever, because that would break “same seed, same land”.&lt;/p&gt;
&lt;p&gt;Water had to work as a rule too. Up to waist depth you wade at falling pace. Deeper, you swim at 0.45 of walking speed, no tools, no fighting, no carrying a log, and stamina drains. Run out and you’re carried to the nearest bank, losing health. From the farthest point of the biggest lake that’s under 80 health, so nobody drowns from full health. Villagers follow the same rules and path round by fords, because swimming costs 40 on the navmesh.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;optimisation-or-why-the-game-froze-at-midnight&quot;&gt;Optimisation, or: why the game froze at midnight&lt;/h2&gt;
&lt;h3 id=&quot;builds-that-took-forever&quot;&gt;Builds that took forever&lt;/h3&gt;
&lt;p&gt;When the land went to 6 km, a clean rebuild of all scenes went to about &lt;strong&gt;24 minutes&lt;/strong&gt;. Most of that wasn’t C# compiling. It was our own generators redoing everything every time.&lt;/p&gt;
&lt;p&gt;So every expensive output now keeps a fingerprint, a set of SHA-256 digests of its inputs: the land’s digest, the domain’s world sources, the generator’s own source, the store-pack assets it reads with their dependencies, and the Unity version. If nothing changed, it’s reused, and the console says so:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Sigilreach] Cache: Oakhollow terrain tiles reused&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Sigilreach] Cache: icons rebuilt (changed: model)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A UI or rules change now needs no terrain rebuild at all.&lt;/p&gt;
&lt;p&gt;The terrain tiles were about 936 MB sitting in &lt;code&gt;Resources&lt;/code&gt;, which meant every player build repacked them. They moved to plain AssetBundles: one per 512 m tile plus a shared bundle, LZ4 compressed so a tile opens without decompressing the whole thing, built once and only copied into a player build. A release build verified at 198 bundles, 974 MB. Tiles stream around the player within 1.6 km (2.4 km on Ultra) and load in the background. And one lesson for free: &lt;strong&gt;building AssetBundles while a player build runs crashes Unity.&lt;/strong&gt; Ask me how I know.&lt;/p&gt;
&lt;h3 id=&quot;the-days-turn&quot;&gt;The day’s turn&lt;/h3&gt;
&lt;p&gt;I played a release build and the game froze for &lt;strong&gt;ten seconds or more&lt;/strong&gt; every time the in-game day changed. Classic. A headless probe on the big land, 302 people, ticking 60 times a second through the hour before midnight, the turn and the hour after, found it in minutes:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;before&lt;/th&gt;
&lt;th&gt;after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The tick at day change&lt;/td&gt;
&lt;td&gt;30 to 42 ms&lt;/td&gt;
&lt;td&gt;7 to 12 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First autosave capture of a session&lt;/td&gt;
&lt;td&gt;3.2 to 3.4 s&lt;/td&gt;
&lt;td&gt;30 to 45 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Later captures&lt;/td&gt;
&lt;td&gt;70 to 95 ms&lt;/td&gt;
&lt;td&gt;27 to 33 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON and the 22 to 29 MB file&lt;/td&gt;
&lt;td&gt;250 to 490 ms on the game thread&lt;/td&gt;
&lt;td&gt;another thread&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The first capture was asking every one of 62,000 resource sources whether it was a felled tree, and working out the land’s trees around each one to answer. Now only trees are asked. Regrowing every stump and fading worn paths moved out of the turn into a round spread over the next in-game hour. The JSON and the write moved off the game thread.&lt;/p&gt;
&lt;p&gt;And then a test guards it: the turning tick may cost at most 3 times the 99th-percentile tick of the hour before it. Measured at 1.4. If someone puts a land-wide loop back into the day’s turn, the test fails before I ever play it.&lt;/p&gt;
&lt;h3 id=&quot;detail-where-it-matters&quot;&gt;Detail where it matters&lt;/h3&gt;
&lt;p&gt;Only villagers within 250 m of you run in full detail and ask their mind. Distant people run a cheaper simulation that has to end up in much the same place, and there’s a test that compares the two over whole worlds. The language model gets one request at a time by priority: your conversation first, then villagers within 30 m, then everyone else. Anything older than an in-game hour is dropped. If the model hasn’t started a spoken reply in 1.5 s, the rules’ answer is said first. About 2.8 s per decision on a macOS build.&lt;/p&gt;
&lt;p&gt;The mind also only &lt;strong&gt;chooses&lt;/strong&gt; among goals the simulation offers, in a JSON schema, and can lift a goal’s score by at most 0.25. Urgent needs still win. A 4B model is a fine source of “living, random-ish choices” and a terrible source of truth, the same lesson I learned with Home Assistant. The rules decide what’s possible.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;testing-fast-without-losing-safety&quot;&gt;Testing: fast without losing safety&lt;/h2&gt;
&lt;p&gt;This is the part I care about most. I’ve written before about &lt;a href=&quot;https://omeratagun.net/blog/tdd-with-ai/&quot;&gt;TDD with AI&lt;/a&gt; and this project is that idea at full volume. Rules are test-first: the failing test, then the minimal code. Agents don’t get to skip it.&lt;/p&gt;
&lt;p&gt;By day six the suite was hurting. An agent’s change took &lt;strong&gt;16+ minutes&lt;/strong&gt; of tests and a merge pass over 30. My instruction was simple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I do not want to reduce the safety of tests, but I want to increase speed and efficiency.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Rules for that change: no test deleted to save time, the same assertions, and every card measured before and after. What came out:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Suite&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Domain rules&lt;/td&gt;
&lt;td&gt;2 min 43 s&lt;/td&gt;
&lt;td&gt;24 to 26 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long whole-world runs&lt;/td&gt;
&lt;td&gt;14 min 57 s&lt;/td&gt;
&lt;td&gt;2 min 24 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PlayMode&lt;/td&gt;
&lt;td&gt;16+ min, most tests loading the full 6 km world&lt;/td&gt;
&lt;td&gt;about 5 min in small scenes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Three things did it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Release builds and parallel fixtures.&lt;/strong&gt; Release alone took the domain tests from 157 s to 43 s. Parallel took it to about 22 s.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Small test scenes.&lt;/strong&gt; Arena for combat, Yard for crafting, Hamlet for building and saves, Castle, Workshop, Commons. Each is about 192 m of the real art, loads in seconds, and is also a place I can open and try one thing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tiers.&lt;/strong&gt; &lt;code&gt;fast&lt;/code&gt; for every agent after every change. &lt;code&gt;world&lt;/code&gt; for the long simulations, queued one at a time per machine. &lt;code&gt;merge&lt;/code&gt; for the lead once per batch. &lt;code&gt;full&lt;/code&gt; before every build I play.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;parallel-tests-found-real-bugs&quot;&gt;Parallel tests found real bugs&lt;/h3&gt;
&lt;p&gt;Running fixtures in parallel immediately broke two tests in about half the runs. The cause: shared caches built with &lt;code&gt;??=&lt;/code&gt;. Two fixtures racing each got their own copy of the content, and a stick from one copy isn’t a stick to a simulation built from the other. They became &lt;code&gt;Lazy&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then an audit found a nastier one. &lt;code&gt;Blueprint.Footprint&lt;/code&gt; was built the same way, and some code compared footprints &lt;strong&gt;by reference&lt;/strong&gt;. Five of six lordly worlds founded in parallel laid out their keep, bailey or hall differently from the same seed run alone. Same seed, different world, only when the race landed. Now there’s a rule (sims share no mutable state) and a test that releases threads together on a barrier and checks they all get one footprint. It failed 17 of 17 runs before the fix.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A simulation whose result depends on what else is running in the process isn’t deterministic. It just hasn’t been caught yet.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;golden-seeds-lie&quot;&gt;Golden seeds lie&lt;/h3&gt;
&lt;p&gt;World tests used to assert on one seed. Then a harmless change shifted the random numbers and seed 3 went from nine hamlets to four. Was that a regression or a seed turning? You can’t tell from one seed. So the world tests now assert &lt;strong&gt;a share of a block of seeds&lt;/strong&gt; (five of eight worlds must pass, for example) run in parallel inside the test. A sweep before and after a change tells drift from a fall.&lt;/p&gt;
&lt;h3 id=&quot;the-nunit-trap&quot;&gt;The NUnit trap&lt;/h3&gt;
&lt;p&gt;Unity ships NUnit 3.5. The fast tier runs on 3.14, because .NET 10 can’t pin anything older. So a test can pass in the cloud and &lt;strong&gt;fail to compile in the editor&lt;/strong&gt;. The one that bites is &lt;code&gt;Does.Contain&lt;/code&gt; on a collection. It’s in &lt;code&gt;AGENTS.md&lt;/code&gt; in bold now.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;working-with-a-crew-of-agents&quot;&gt;Working with a crew of agents&lt;/h2&gt;
&lt;p&gt;None of the above happens without process, because several agents work on the same code at the same time. What we landed on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A lead session on the Mac&lt;/strong&gt; that integrates. Agents push branches, never &lt;code&gt;main&lt;/code&gt;. The lead merges a batch, runs fast, then the affected world tests, the editor compile, a rebuild if a generator changed, the visual checks, and only then pushes. If a batch fails, &lt;code&gt;git bisect --first-parent&lt;/code&gt; finds the branch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A Windows desktop for editor work.&lt;/strong&gt; It has the Unity editor and the packs, so PlayMode tests, rebuilds and experiments run there, one at a time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud sessions for rules.&lt;/strong&gt; No editor, but the domain compiles and runs anywhere with .NET, so they write rules and tests and mark their PRs “PlayMode not run”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs change in the same commit as the code.&lt;/strong&gt; &lt;code&gt;SYSTEMS.md&lt;/code&gt;, &lt;code&gt;TESTING.md&lt;/code&gt; and the feature cards are what the next agent reads. A change that leaves a doc saying something false is not done. The docs are around 6,000 lines now and they’re the project’s memory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Small files, one concern each.&lt;/strong&gt; Several agents in one big file is a merge conflict factory. New goals get new files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No runtime singletons.&lt;/strong&gt; No &lt;code&gt;static Instance&lt;/code&gt;. Systems are handed to what uses them, wired by the scene generator. This one came from a bug: the pause, sleep and the death skip each saved and restored &lt;code&gt;Time.timeScale&lt;/code&gt;, and one restoring after another left the game at &lt;strong&gt;8x speed&lt;/strong&gt;. Now one owner holds the speed and everyone else takes and releases holds.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;an-audit-day&quot;&gt;An audit day&lt;/h3&gt;
&lt;p&gt;Today was an audit day. A code audit produced a findings page, and the desktop took three branches. The interesting one: raised floors in the castle’s buildings were answering at ground height, half a metre low. The brief suspected Unity’s navmesh height mesh. The desktop wrote a probe that sampled every deck cell over four new games, ran it with the height mesh on and off, and found &lt;strong&gt;the height mesh wasn’t the cause&lt;/strong&gt;. Bodies were being placed before the buildings were in the mesh on a load. Main had 70 of 156 deck cells wrong. The branch with the load wait had 0 of 104.&lt;/p&gt;
&lt;p&gt;Same branch: creatures were being baked into the navmesh as solid capsules. A new test showed a 0.70 m hole around a deer without the fix. A reviewer then pointed out felled trunks had no test, so the next PR added one: a 0.83 m hole without the fix.&lt;/p&gt;
&lt;p&gt;The other one I liked: a castle walk test was flaky “about 1 run in 4” on the Mac. The desktop probed 40 loads, half with the height mesh on and half off, then ran the real test 10 times on the branch and 10 on main. It &lt;strong&gt;never failed&lt;/strong&gt;. So it changed nothing and wrote down the numbers and the suspect. No fix without a failure to test it against. That’s the right call, and it’s not one I expected an agent to make on its own.&lt;/p&gt;
&lt;h3 id=&quot;things-agents-are-still-bad-at&quot;&gt;Things agents are still bad at&lt;/h3&gt;
&lt;p&gt;Honest list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Looking at the game.&lt;/strong&gt; Agents don’t judge visuals. I do, in the editor, at day, golden hour and night. Every visual card ends with “owner checks” written as pass and fail, and a lot of them are still pending.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Knowing when “it works” isn’t “it’s right”.&lt;/strong&gt; A world can pass every test and still look wrong when you walk through it. Only playing catches that.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Animations.&lt;/strong&gt; I had to make it a rule: an animation finishes before the next action starts, for everyone. Lying down, getting up, kneeling into work. Without it everything looks glitchy. It’s data on the generated animator now (&lt;code&gt;Settle&lt;/code&gt; and &lt;code&gt;Settled&lt;/code&gt; tags), not a timer per case.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;what-id-do-the-same-way-again&quot;&gt;What I’d do the same way again&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Engine-free rules from day one.&lt;/strong&gt; It’s the reason tests take a minute, cloud agents are useful, and villagers can do everything the player does.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generators, not hand-made scenes.&lt;/strong&gt; Painful to set up, but every scene is reproducible, reviewable as code and cacheable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Measure before optimising.&lt;/strong&gt; Every speed-up in this post started with a probe that printed numbers, and ended with a test that keeps them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs as the shared brain.&lt;/strong&gt; With many sessions and none of them remembering yesterday, the docs are the only continuity there is.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URP.&lt;/strong&gt; I haven’t missed HDRP once. I’ve missed good assets many times.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;
&lt;p&gt;The villagers’ AI gets a regression net next: a set of measures over many seeds that says whether a change made the world better or worse, before I play it. Then bows, sieges, the castle redesign, footsteps that make a sound, and a lot of owner checks I owe the crew.&lt;/p&gt;
&lt;p&gt;Nine days in, the world already does things I didn’t plan. A home-dreamer builds a lean-to and a kind neighbour helps. A greedy hoarder gets caught pilfering and resented. Men sent on an outlaw hunt sided with the outlaws because family and friends of the band were among the hunters, and now there’s a test for that.&lt;/p&gt;
&lt;p&gt;That’s the whole idea. I just have to keep up with it.&lt;/p&gt;
&lt;p&gt;Till the next one. Stay hydrated.&lt;/p&gt;</content:encoded><category>gamedev</category><category>unity</category><category>urp</category><category>sigilreach</category><category>claude code</category><category>ai coding agents</category><category>TDD</category><category>optimisation</category><category>testing</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Cameras, a local LLM, and a pile of wrong assumptions</title><link>https://omeratagun.net/blog/cameras-local-llm-wrong-assumptions/</link><guid isPermaLink="true">https://omeratagun.net/blog/cameras-local-llm-wrong-assumptions/</guid><description>I pointed a spare 3090 at four cameras and a house full of Home Assistant entities to build a fully local NVR with a local voice assistant. Most of what I assumed going in turned out to be wrong. Here is what actually held up.</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My infrastructure has two halves. The public half is a small HA k3s cluster on Hetzner, all GitOps through ArgoCD, which is where my actual projects live. The other half is home: a machine with an RTX 3090 in it that spends most of its life at six percent GPU load.&lt;/p&gt;
&lt;p&gt;That felt wasteful. The 3090 is idle, I have a symmetric gigabit line, and I have TP-Link cameras that dump footage onto SD cards where it goes to die. So the plan was simple. Make the home box the &lt;em&gt;private, heavy, local&lt;/em&gt; plane. NVR, camera AI, a voice assistant that never phones home. Nothing that needs to be public, nothing that needs five-nines. That stays on Hetzner.&lt;/p&gt;
&lt;p&gt;What started as “let me just run Frigate” turned into a week of finding out which of my assumptions were load-bearing and which were decoration. Most were decoration.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every “this should just work” in home automation is a small bet against physics or against a vendor. You usually lose both.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;what-actually-got-built&quot;&gt;What actually got built&lt;/h2&gt;
&lt;p&gt;The end state, before I get into everything that went sideways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://frigate.video/&quot;&gt;Frigate&lt;/a&gt;&lt;/strong&gt; on the 3090 box, plain Docker Compose rather than k3s. Frigate wants host networking and GPU passthrough, and fighting Kubernetes for that is not a good time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Four Tapo cameras&lt;/strong&gt; pulled over RTSP, detection running on the GPU, 24/7 recording landing on a dedicated 2 TB disk with a 14-day retention window.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object detection&lt;/strong&gt; at ~2.4 ms per inference. The GPU does not notice four cameras.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://ollama.com/&quot;&gt;Ollama&lt;/a&gt; on the same box&lt;/strong&gt; doing two jobs: a vision model that writes a sentence about every person the cameras see, and a text model wired into Home Assistant as a conversation agent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A local voice pipeline.&lt;/strong&gt; &lt;code&gt;faster-whisper&lt;/code&gt; for speech-to-text on the GPU, Piper for text-to-speech, glued to Home Assistant Assist. You talk to it from your phone, it answers, nothing leaves the LAN.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frigate talks to Home Assistant over MQTT&lt;/strong&gt;, so every camera, zone, and detection is an entity I can build automations on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That all works. Getting there is the interesting part.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;wrong-assumption-1-the-gpu-will-just-run-a-good-detector&quot;&gt;Wrong assumption #1: “the GPU will just run a good detector”&lt;/h2&gt;
&lt;p&gt;Frigate 0.17 ships references to nice modern detectors like RF-DETR and YOLO-NAS. I assumed it would download one and go. It does not. Those paths only auto-resolve for paid Frigate+ models. With a plain setup you either bring your own ONNX file or you run the bundled CPU model, which Frigate itself tells you is for testing only.&lt;/p&gt;
&lt;p&gt;So I exported my own. YOLO11n, in a throwaway container:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --rm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -v&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;$(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;pwd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)/model_cache:/out&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /out&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; python:3.11-slim&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bash&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -c&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  apt-get update -qq &amp;amp;&amp;amp; apt-get install -y -qq --no-install-recommends libgl1 libglib2.0-0 libxcb1 &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  pip install -q ultralytics onnx onnxslim &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  yolo export model=yolo11n.pt format=onnx imgsz=320 simplify=True opset=12&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Point Frigate’s &lt;code&gt;onnx&lt;/code&gt; detector at that file with &lt;code&gt;model_type: yolo-generic&lt;/code&gt; (note the hyphen, the un-hyphenated version fails config validation, which cost me a restart to notice), and it runs on the TensorRT execution provider. YOLO11n at 320px is a tiny model. The input tensor is 320×320 no matter what resolution you feed it, so the detector cost is basically flat whether the camera is 720p or 1080p.&lt;/p&gt;
&lt;p&gt;Lesson: the detector was never the hard part.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;wrong-assumption-2-face-recognition-is-a-model-problem&quot;&gt;Wrong assumption #2: “face recognition is a model problem”&lt;/h2&gt;
&lt;p&gt;This is the one I was most wrong about, and it took real data to accept.&lt;/p&gt;
&lt;p&gt;Frigate 0.17 has native face recognition: a face detector plus ArcFace embeddings, all on the GPU. I enrolled myself, my wife, my daughter. It worked occasionally. Most detections came back as &lt;code&gt;unknown&lt;/code&gt; with a similarity score around &lt;strong&gt;0.02&lt;/strong&gt;. That is not “just under the threshold”. 0.02 is &lt;em&gt;no relationship to the enrolled face at all&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I dug into the captured crops. They were 25 to 40 pixels of pixelated mush. Here is why, and it has nothing to do with ArcFace.&lt;/p&gt;
&lt;p&gt;My cameras are mounted on the eaves, roughly three metres up, wide angle, looking &lt;em&gt;down&lt;/em&gt; at the yard. A person walking to the door is five to ten metres away. By this point I had already moved detection to the full main stream, up from the 360p substream. Even so, at 1080p the &lt;strong&gt;face&lt;/strong&gt; in that frame is maybe 70 pixels tall on a good day, viewed from a steep angle so the camera sees forehead and scalp, usually with motion blur because the person is walking.&lt;/p&gt;
&lt;p&gt;The one time recognition hit 0.95 was when I deliberately stopped and looked up at the camera during enrollment.&lt;/p&gt;
&lt;p&gt;Things that do &lt;strong&gt;not&lt;/strong&gt; fix this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Labeling the bad captures. Feeding top-down blurry non-frontal crops into the reference set drags the embedding centroid toward garbage and starts producing &lt;em&gt;false positives&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Lowering &lt;code&gt;recognition_threshold&lt;/code&gt;. At 0.02 similarity, lowering the bar just means the system confidently mislabels strangers.&lt;/li&gt;
&lt;li&gt;“Recording at higher resolution.” The Tapo C200/C500 class cameras are 2 MP sensors, so 1080p &lt;strong&gt;is&lt;/strong&gt; the ceiling. There is no higher stream to pull. Asking for more would be upscaling, which adds zero information. Face pixels are a function of sensor resolution × lens FOV × distance × angle, and the stream setting is none of those.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;A 70-pixel, top-down, motion-blurred face is a 70-pixel, top-down, motion-blurred face regardless of what you type into the config.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The actual fix is a camera doing a different job. One unit at ~1.7 m, near-horizontal, at a chokepoint everyone walks through facing it, so an approaching face is 100 to 150 px and roughly frontal. That is a doorbell, essentially. The eaves cameras stay as &lt;em&gt;area&lt;/em&gt; cameras, doing person/zone/time logic, not identity. Identity becomes per-camera: face recognition at the door, licence-plate recognition on the driveway, and “someone is in the zone after dark while nobody’s home” everywhere else.&lt;/p&gt;
&lt;p&gt;I had not mounted that camera yet when I stopped, so face-gated automations are on hold. But knowing &lt;em&gt;why&lt;/em&gt; it was not working was worth the week on its own.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;wrong-assumption-3-a-local-7b-model-can-run-the-house&quot;&gt;Wrong assumption #3: “a local 7B model can run the house”&lt;/h2&gt;
&lt;p&gt;I wired &lt;code&gt;qwen2.5:7b&lt;/code&gt; into Home Assistant as a conversation agent with the Assist API exposed, so it could actually control devices. Then I said “turn off the garden lights” six times. Four times it cheerfully said “done” and nothing happened. Twice it said it couldn’t find the device.&lt;/p&gt;
&lt;p&gt;The model wasn’t entirely to blame. Home Assistant’s own intent engine gave me the real error:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sorry, there are multiple devices called garden lights&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had broken &lt;code&gt;template&lt;/code&gt; switches. &lt;code&gt;switch.garden_lights&lt;/code&gt;, stuck in an &lt;code&gt;unknown&lt;/code&gt; state, was shadowing the real SmartThings entity that was also called “Garden lights”. Home Assistant refuses to act on an ambiguous name. The LLM, given the same ambiguity, just hallucinated success. On top of that, roughly sixty useless entities were exposed to Assist: dishwasher program toggles, every camera’s privacy and patrol switch, “santa”. More things for a small model to pick wrong from.&lt;/p&gt;
&lt;p&gt;The fixes, in order of impact:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Delete the broken duplicates.&lt;/strong&gt; Ambiguity gone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trim what’s exposed to Assist&lt;/strong&gt; down to real controllables: lights, switches, locks, media players, climate. 78 entities to 34.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;prefer_local_intents: true&lt;/code&gt;&lt;/strong&gt; on the Assist pipeline. Now “turn off the garden lights” is matched by Home Assistant’s deterministic intent parser, with no LLM in the loop at all. The model only gets involved for actual conversation: “how many lights are on”, “is the front door locked”.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After that, device control is instant and reliable. The lesson is that a 7B model is a bad switchboard operator and a fine conversationalist. Don’t make it do the job the rule engine already does perfectly.&lt;/p&gt;
&lt;p&gt;Also, purely operational: Ollama binds to &lt;code&gt;127.0.0.1&lt;/code&gt; by default, so Home Assistant on another box couldn’t reach it. &lt;code&gt;OLLAMA_HOST=0.0.0.0&lt;/code&gt; in a systemd drop-in, plus a &lt;code&gt;ufw&lt;/code&gt; rule scoped to the HA box:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ufw&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; allow&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 192.168.50.217&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; port&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 11434&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; proto&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tcp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;ollama for HA&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had hit the exact same firewall wall earlier with Frigate’s port 5000. The home box runs default-deny inbound, which is correct, and which I forget every single time.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;wrong-assumption-4-the-google-speakers-can-be-my-voice-assistant&quot;&gt;Wrong assumption #4: “the Google speakers can be my voice assistant”&lt;/h2&gt;
&lt;p&gt;I have Nest speakers in the kitchen and office. The dream was: talk to the room, my local LLM answers through the same speaker, no Google cloud involved.&lt;/p&gt;
&lt;p&gt;You cannot do this. “Hey Google” is hardwired to Google Assistant, and Nest speakers do not expose their microphone to Home Assistant or anything else. Sending TTS &lt;em&gt;to&lt;/em&gt; them works fine. That half is solved, and it’s how the announcements play. But the house cannot &lt;em&gt;hear&lt;/em&gt; you through them.&lt;/p&gt;
&lt;p&gt;The real path is a dedicated voice satellite: a small mic and speaker running its own local wake word, streaming to Assist, which runs whisper on the GPU, then the LLM, then Piper back to the satellite. Fully local, a couple of seconds round trip. Until that hardware shows up, the phone’s Assist button does the same job with no hands-free.&lt;/p&gt;
&lt;p&gt;Bonus gotcha from the same evening: &lt;code&gt;media_player.living_room&lt;/code&gt; turned out to be an &lt;strong&gt;Apple TV&lt;/strong&gt;, not a speaker. Casting TTS to it threw &lt;code&gt;RuntimeError: not connected to remote&lt;/code&gt; and would have interrupted whatever was playing anyway. Half of home automation is discovering what your entities actually are.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;what-held-up&quot;&gt;What held up&lt;/h2&gt;
&lt;p&gt;For all the wrong turns, a surprising amount is genuinely good:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GPU object detection.&lt;/strong&gt; 2.4 ms inference, four cameras, the GPU basically idle. Room for a dozen more.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;24/7 recording&lt;/strong&gt; on its own disk. Frigate self-prunes when the disk fills, so retention is bounded without babysitting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Semantic search&lt;/strong&gt; over footage. CLIP embeddings land on every event, so I can type “person with a backpack” and get clips.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The vision descriptions are legitimately useful.&lt;/strong&gt; &lt;code&gt;qwen2.5vl:7b&lt;/code&gt;, running locally, produces things like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There’s a delivery courier in a yellow vest at the door, setting down a large box.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;from a single snapshot. That’s a phone notification worth reading.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Local voice device control&lt;/strong&gt;, after the cleanup, is fast and reliable, and every word of it stays on the LAN.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;the-end-goal&quot;&gt;The end goal&lt;/h2&gt;
&lt;p&gt;The point of all this isn’t “cameras”. It’s a &lt;strong&gt;local plane&lt;/strong&gt; that mirrors the design of the cloud one, just optimised for the opposite constraints.&lt;/p&gt;
&lt;p&gt;Hetzner and ArgoCD are for things that must be public and highly available. The home box is for things that must be &lt;strong&gt;private, cheap to run heavy, and physically here&lt;/strong&gt;: the NVR, the footage, the photos, the passwords, the automations that touch the actual house.&lt;/p&gt;
&lt;p&gt;Concretely, where this is headed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A house that &lt;strong&gt;understands what it sees&lt;/strong&gt;. Not “motion detected”, but “a courier left a package” or “someone’s been standing at the gate for two minutes”, with the vision model deciding whether an event is worth interrupting anyone over.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identity done right, per camera.&lt;/strong&gt; A real face camera at the door, plate recognition on the driveway, zone-and-time logic everywhere else. No more pretending the eaves cameras can recognise faces.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A voice assistant that never leaves the network.&lt;/strong&gt; Whisper, an LLM, and Piper all on the 3090, a satellite in the kitchen, Google’s speakers demoted to output-only.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automations that fuse camera events with house state.&lt;/strong&gt; Presence, the alarm, sunset, who’s home. Today either the camera or Home Assistant works blind, and that should not be the case.&lt;/li&gt;
&lt;li&gt;The 3090 doing actual work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of it depends on a subscription, a cloud, or a vendor deciding to sunset a feature. If the internet drops, the house still watches itself and still answers when you talk to it.&lt;/p&gt;
&lt;p&gt;That’s the whole idea. The cameras were just where I started finding out how much of it I had wrong.&lt;/p&gt;
&lt;p&gt;Till the next one. Stay hydrated.&lt;/p&gt;</content:encoded><category>home server</category><category>self hosted</category><category>frigate</category><category>nvr</category><category>home assistant</category><category>ollama</category><category>local ai</category><category>computer vision</category><category>automation</category><category>HA</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>From kubectl apply to sleeping well at night</title><link>https://omeratagun.net/blog/from-kubectl-to-sleeping-well/</link><guid isPermaLink="true">https://omeratagun.net/blog/from-kubectl-to-sleeping-well/</guid><description>How I migrated my entire home cluster from manual kubectl commands to a full GitOps setup with ArgoCD, HA, observability, and one-command disaster recovery.</description><pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I had this cluster running for a while. Single node k3s, couple of apps, deployments done through CI pipelines that would &lt;code&gt;kubectl apply&lt;/code&gt; manifests directly into the cluster. It worked. Until it didn’t feel right anymore.&lt;/p&gt;
&lt;p&gt;Every time I thought about “what if this machine dies”, the answer was a shrug and a prayer. Secrets were created manually on the cluster. Database backups? Didn’t exist. If that VM went down, I’d be rebuilding everything from memory and scattered YAML files across repositories.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“If your disaster recovery plan is ‘I’ll remember’, you won’t.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I decided to fix it properly. What started as “let me just set up ArgoCD” turned into a full infrastructure overhaul that I’m honestly proud of. Here’s the journey.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-1-argocd-and-the-gitops-mindset&quot;&gt;Chapter 1: ArgoCD and the GitOps Mindset&lt;/h2&gt;
&lt;p&gt;The first thing was getting ArgoCD running and moving all deployments under its control. The idea is simple — one Git repository is the source of truth for everything running in the cluster. Push to main, ArgoCD syncs.&lt;/p&gt;
&lt;p&gt;I created &lt;code&gt;softastik-infra&lt;/code&gt;, an infrastructure repository that holds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All Kubernetes manifests (deployments, services, ingresses)&lt;/li&gt;
&lt;li&gt;Helm values for any chart-based apps&lt;/li&gt;
&lt;li&gt;ArgoCD Application CRDs that point to their respective directories&lt;/li&gt;
&lt;li&gt;SealedSecret manifests for encrypted credentials&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The CI pipelines in app repositories now just build an image, push it, and update the image tag in the infra repo. That’s it. ArgoCD picks up the change and rolls it out.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ArgoCD Application — one per app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;apiVersion&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;argoproj.io/v1alpha1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Application&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;metadata&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;tenexengineer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  namespace&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;argocd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spec&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  project&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;softastik&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  source&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    repoURL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https://github.com/softastik-org/softastik-infra.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    path&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;apps/tenexengineer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  destination&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https://kubernetes.default.svc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    namespace&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;tenex&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  syncPolicy&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    automated&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      selfHeal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      prune&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;selfHeal: true&lt;/code&gt; means if someone manually changes something on the cluster, ArgoCD reverts it. &lt;code&gt;prune: true&lt;/code&gt; means if I delete a manifest from Git, the resource gets deleted from the cluster. Git is the boss now.&lt;/p&gt;
&lt;h3 id=&quot;sealed-secrets--because-credentials-in-git-sounds-terrible&quot;&gt;Sealed Secrets — because credentials in Git sounds terrible&lt;/h3&gt;
&lt;p&gt;Obviously you can’t commit plain secrets to a Git repo. &lt;a href=&quot;https://github.com/bitnami-labs/sealed-secrets&quot;&gt;Bitnami Sealed Secrets&lt;/a&gt; solves this. You encrypt secrets with a public key, commit the encrypted version, and only the controller running in the cluster can decrypt them.&lt;/p&gt;
&lt;p&gt;The critical thing: &lt;strong&gt;back up the encryption key&lt;/strong&gt;. Without it, a new cluster can’t decrypt your existing sealed secrets. That backup file is the one piece that lives outside of Git, and it’s the foundation of the entire recovery process.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# This one command is your insurance policy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;kubectl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; secret&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; kube-system&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  -l&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sealedsecrets.bitnami.com/sealed-secrets-key&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; yaml&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/sealed-secrets-key-backup.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-2-shared-postgres--one-database-to-rule-them-all&quot;&gt;Chapter 2: Shared Postgres — One Database to Rule Them All&lt;/h2&gt;
&lt;p&gt;Each app was running its own Postgres instance. On a home cluster with limited resources, that’s wasteful. More importantly, it means N separate backup strategies.&lt;/p&gt;
&lt;p&gt;I consolidated everything into a shared Postgres instance running &lt;code&gt;pgvector/pgvector:pg17&lt;/code&gt;. Each app gets its own database and user, created by an init script:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; USER&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; tenex&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; WITH&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;${TENEX_PASSWORD}&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; DATABASE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; tenexengineer&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; OWNER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tenex;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;\c tenexengineer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EXTENSION &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;IF&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; EXISTS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding a new app means adding a few lines to the init ConfigMap. One Postgres pod, one backup job, one restore process.&lt;/p&gt;
&lt;h3 id=&quot;automated-backups&quot;&gt;Automated Backups&lt;/h3&gt;
&lt;p&gt;A CronJob runs &lt;code&gt;pg_dumpall&lt;/code&gt; every night at 3 AM, gzips it, uploads to S3, and cleans up backups older than 7 days:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;schedule&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;0 3 * * *&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. ArgoCD deploys the CronJob, and it just runs. No cron on the host machine, no scripts to forget about.&lt;/p&gt;
&lt;h3 id=&quot;backup-verification&quot;&gt;Backup Verification&lt;/h3&gt;
&lt;p&gt;Here’s the thing about backups — they’re worthless if you’ve never tested a restore. So I added a weekly verification job that downloads the latest backup, restores it into a temporary database, checks that tables and data exist, then drops it:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;=== Backup Verification PASSED ===&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  File: all_databases_2026-04-13_0300.sql.gz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Size: 847293 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Tables: 24&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it fails, the existing alert rules catch it and Slack yells at me.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-3-the-what-if-moment--one-command-recovery&quot;&gt;Chapter 3: The “What If” Moment — One Command Recovery&lt;/h2&gt;
&lt;p&gt;With ArgoCD managing deployments, Sealed Secrets handling credentials, and backups going to S3, I realized I was close to something powerful: &lt;strong&gt;a one-command cluster rebuild&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I wrote &lt;code&gt;scripts/bootstrap-cluster.sh&lt;/code&gt; that does everything:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Installs k3s&lt;/li&gt;
&lt;li&gt;Installs Helm&lt;/li&gt;
&lt;li&gt;Installs Sealed Secrets controller and restores the encryption key&lt;/li&gt;
&lt;li&gt;Installs ArgoCD&lt;/li&gt;
&lt;li&gt;Connects the private infra repo&lt;/li&gt;
&lt;li&gt;Applies all ArgoCD Applications&lt;/li&gt;
&lt;li&gt;Waits for everything to sync&lt;/li&gt;
&lt;li&gt;Restores the database from S3&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# New machine? One command.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;./scripts/bootstrap-cluster.sh&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/sealed-secrets-key-backup.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That script is the answer to “what if this machine dies”. The answer is: get a new machine, run one script, update DNS. Done.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-4-going-ha--because-single-points-of-failure-are-boring&quot;&gt;Chapter 4: Going HA — Because Single Points of Failure Are Boring&lt;/h2&gt;
&lt;p&gt;At some point I looked at this beautiful setup running on a single VM and thought… that’s still one machine. One disk failure away from downtime.&lt;/p&gt;
&lt;p&gt;So I got three servers and went full HA.&lt;/p&gt;
&lt;h3 id=&quot;k3s-with-embedded-etcd&quot;&gt;k3s with embedded etcd&lt;/h3&gt;
&lt;p&gt;k3s supports HA clustering with embedded etcd — no external database needed. Three server nodes give you quorum. If one dies, the cluster keeps running.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# First node initializes the cluster&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -sfL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://get.k3s.io&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  --cluster-init&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  --tls-san&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 178.104.173.177&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  --disable&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; local-storage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;longhorn-for-distributed-storage&quot;&gt;Longhorn for distributed storage&lt;/h3&gt;
&lt;p&gt;With multiple nodes, you need storage that replicates across them. &lt;a href=&quot;https://longhorn.io&quot;&gt;Longhorn&lt;/a&gt; does exactly that. Postgres writes to a Longhorn volume, Longhorn replicates the data to all three nodes. A node dies? The volume is still available from the other two.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;NAME                STATE      ROBUSTNESS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;shared-postgres     attached   healthy     # 3 replicas across 3 nodes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No more single disk as a point of failure.&lt;/p&gt;
&lt;h3 id=&quot;argocd-itself-runs-ha&quot;&gt;ArgoCD itself runs HA&lt;/h3&gt;
&lt;p&gt;Two replicas for the API server, two for the repo server. The bootstrap script and Helm values handle this automatically.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-5-observability--because-guessing-is-not-monitoring&quot;&gt;Chapter 5: Observability — Because Guessing Is Not Monitoring&lt;/h2&gt;
&lt;p&gt;A cluster without observability is just a fancy way to run things you can’t see. I added the full stack:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prometheus&lt;/strong&gt; — scrapes metrics from all cluster components and applications. 15 days retention, 10Gi persistent storage on Longhorn.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Grafana&lt;/strong&gt; — dashboards for everything. I created four as code (ConfigMaps auto-discovered by the Grafana sidecar):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes Apps Overview&lt;/strong&gt; — CPU, memory, restarts, network I/O per pod&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TenexEngineer&lt;/strong&gt; — request rate, latency percentiles (p50/p95/p99), error rate, pipeline metrics&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared Postgres&lt;/strong&gt; — pod status, uptime, backup status, PVC usage, disk I/O&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uptime &amp;amp; Availability&lt;/strong&gt; — endpoint status, response times, SSL expiry, 30-day uptime percentage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Alertmanager&lt;/strong&gt; — fires alerts to Slack. Custom rules for the things that matter:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;NodeDown&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;          # server unreachable for 2 min&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;PodCrashLooping&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # pod restarting repeatedly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;HighMemoryUsage&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # node memory above 90%&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;PostgresDown&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # shared postgres not ready&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;BackupCronJobFailed&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # nightly backup or weekly verify failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;LonghornVolumeUnhealthy&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # storage replication degraded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;EndpointDown&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # public endpoint unreachable&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Blackbox Exporter&lt;/strong&gt; — external uptime checks. Every 30 seconds it probes the blog, tenexengineer, ArgoCD, and Grafana from inside the cluster.&lt;/p&gt;
&lt;p&gt;All of this is deployed through ArgoCD as Helm charts with multi-source Applications. Values live in the infra repo, charts come from the prometheus-community Helm repository. Push a change to alert rules? ArgoCD syncs it. Add a new blackbox target? Same thing.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;chapter-6-application-level-metrics&quot;&gt;Chapter 6: Application-Level Metrics&lt;/h2&gt;
&lt;p&gt;The cluster-level metrics were great, but I also wanted to see what’s happening inside my apps. So I added a Prometheus middleware to TenexEngineer — it records request count, latency histograms, and in-flight requests for every endpoint:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Three new metrics, automatically recorded for every request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HTTPRequestDuration   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// histogram by method, path, status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HTTPRequestsTotal     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// counter by method, path, status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HTTPRequestsInFlight  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// gauge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A ServiceMonitor tells Prometheus to scrape it, and a Grafana dashboard visualizes it. Now I can see exactly how many webhook events TenexEngineer processes, how long the pipeline takes, and whether anything is erroring out.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;whats-actually-in-git-now&quot;&gt;What’s Actually in Git Now&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;In Git (automatic)&lt;/th&gt;
&lt;th&gt;NOT in Git (manual setup)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All Kubernetes manifests&lt;/td&gt;
&lt;td&gt;k3s installation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SealedSecret manifests&lt;/td&gt;
&lt;td&gt;Sealed Secrets encryption key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ArgoCD Application CRDs&lt;/td&gt;
&lt;td&gt;ArgoCD repo credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helm values (monitoring, blackbox)&lt;/td&gt;
&lt;td&gt;DNS records (Cloudflare)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingress rules&lt;/td&gt;
&lt;td&gt;Longhorn installation (HA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup CronJob + Verify Job&lt;/td&gt;
&lt;td&gt;GitHub repo secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom Prometheus alert rules&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana dashboards as code&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blackbox uptime targets&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The “NOT in Git” column is the migration checklist. Everything else rebuilds itself.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;the-end-result&quot;&gt;The End Result&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ kubectl get applications -n argocd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;NAME                HEALTH    SYNC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;blog                Healthy   Synced&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dashboards          Healthy   Synced&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;tenexengineer       Healthy   Synced&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;shared              Healthy   Synced&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;prometheus-stack    Healthy   Synced&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;blackbox-exporter   Healthy   Synced&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three nodes. Six apps. Automated backups with verification. Full observability with Slack alerting. Grafana dashboards for every component. One-command disaster recovery.&lt;/p&gt;
&lt;p&gt;From &lt;code&gt;kubectl apply&lt;/code&gt; and hoping for the best, to sleeping well at night knowing that if everything burns down, one script and a DNS update brings it all back.&lt;/p&gt;
&lt;p&gt;Was it overkill for a home cluster? Absolutely. Did I learn a ton and have fun doing it? Also absolutely.&lt;/p&gt;
&lt;p&gt;Till next time, keep your sealed secrets backed up.&lt;/p&gt;</content:encoded><category>blog</category><category>argocd</category><category>gitops</category><category>kubernetes</category><category>k3s</category><category>prometheus</category><category>grafana</category><category>home server</category><category>self hosted</category><category>devops</category><category>HA</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>TDD with AI</title><link>https://omeratagun.net/blog/tdd-with-ai/</link><guid isPermaLink="true">https://omeratagun.net/blog/tdd-with-ai/</guid><description>How to design systems with AI while keeping your sanity</description><pubDate>Sat, 07 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ever since I started writing about testing with AI on this very blog, I’ve had quite the journey at work  trying to balance raw speed with the experience and scars I’ve accumulated over time.&lt;/p&gt;
&lt;p&gt;This is the story of how I leveraged AI &lt;em&gt;without losing my sanity&lt;/em&gt;, using a TDD-first mindset. Wear your helmets, tighten your seatbelts  let’s go.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;optimisation-opportunity&quot;&gt;Optimisation opportunity&lt;/h2&gt;
&lt;p&gt;Imagine a graph we need to traverse as a tree. We need traversal in &lt;strong&gt;both directions&lt;/strong&gt;: forward and reverse.&lt;/p&gt;
&lt;p&gt;The initial idea was intentionally naive:
get it working with a very basic implementation where database calls scale with the number of hops. The goal wasn’t performance  it was &lt;strong&gt;shape&lt;/strong&gt;. I wanted the input and output to be exactly right so we could lock behavior down with tests &lt;em&gt;before&lt;/em&gt; touching optimisation.&lt;/p&gt;
&lt;p&gt;So that’s what we did.&lt;/p&gt;
&lt;p&gt;We wrote stress tests and benchmarks using testcontainers to stay close to production behavior. Inputs and outputs were spot-on. Then we layered in extra specifications as unit and integration tests.&lt;/p&gt;
&lt;p&gt;Now the fun part begins.&lt;/p&gt;
&lt;p&gt;I told the AI to optimise  not with “make this faster”, but by giving direction and constraints about &lt;em&gt;how&lt;/em&gt; to think about the problem.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;i-put-ai-to-the-test-do-something-vs-be-instructed&quot;&gt;I put AI to the test: “do something” vs “be instructed”&lt;/h2&gt;
&lt;h3 id=&quot;do-something&quot;&gt;“Do something”&lt;/h3&gt;
&lt;p&gt;I said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Go look into this service, analyse it, and come back with optimisation opportunities.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sonnet 4.5 did what most engineers do when skimming unfamiliar code:
“Let’s cache this bad boy.”&lt;/p&gt;
&lt;p&gt;Early returns, hashmaps, tracking visited nodes in memory  all technically reasonable ideas.&lt;/p&gt;
&lt;p&gt;Except… we run &lt;strong&gt;multiple pods&lt;/strong&gt; in a Kubernetes cluster.&lt;/p&gt;
&lt;p&gt;Each pod would maintain its own in-memory cache with its own TTL. Consistency becomes fuzzy, cache warm-up becomes painful, and until caches stabilize, each node still fires hundreds of queries per graph traversal.&lt;/p&gt;
&lt;p&gt;Yes, you &lt;em&gt;could&lt;/em&gt; try to fix this with shared cache hit/miss tracking, but that’s not optimisation  that’s complexity debt.&lt;/p&gt;
&lt;h3 id=&quot;do-a-single-recursive-cte-query&quot;&gt;“Do a single recursive CTE query”&lt;/h3&gt;
&lt;p&gt;This time I gave a very explicit instruction:
turn the traversal into a &lt;strong&gt;single recursive CTE query&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It took hours. But eventually, we got there.&lt;/p&gt;
&lt;p&gt;A massive query with lots of bindings, covering both the base and recursive steps. Proper composite indexes, well thought-out relations  all technically solid.&lt;/p&gt;
&lt;p&gt;I was positively surprised.&lt;/p&gt;
&lt;p&gt;This was the real difference between &lt;em&gt;“figure something out”&lt;/em&gt; and &lt;em&gt;“solve this problem in this way”&lt;/em&gt;.
AI is a statistical model. It optimizes for correctness, not for &lt;em&gt;appropriateness&lt;/em&gt;. Its first suggestion was reasonable, but it didn’t even glance at the k8s setup  so it couldn’t possibly know caching was the wrong trade-off.&lt;/p&gt;
&lt;p&gt;Once guided, it delivered exactly what I asked for. But that came with a hidden cost.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;the-blind-spot-it-works-vs-it-scales&quot;&gt;The blind spot: “it works” vs “it scales”&lt;/h2&gt;
&lt;p&gt;At low data volume, the recursive CTE felt fine.&lt;/p&gt;
&lt;p&gt;But real production data isn’t small  and &lt;strong&gt;recursive queries don’t degrade linearly&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Under the hood, Postgres was evaluating a join condition that &lt;em&gt;looked&lt;/em&gt; harmless:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;… &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;JOIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; edges &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; from_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; current_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;OR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; to_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; current_id …&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;OR&lt;/code&gt; inside a recursive join is a silent killer.&lt;/p&gt;
&lt;p&gt;The planner can’t commit to a single index path, so each recursion level multiplies the amount of work. Scans grow, merges explode, and execution plans balloon.&lt;/p&gt;
&lt;p&gt;Without a defensive approach, the outcome would have been predictable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;massive execution plans&lt;/li&gt;
&lt;li&gt;queries that look correct but &lt;em&gt;never finish&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thankfully, I didn’t discover this on a customer’s bill  at least not this time. That’s why it’s called experience.&lt;/p&gt;
&lt;p&gt;Yet again, I had to explicitly tell Sonnet that &lt;strong&gt;timeouts were non-negotiable&lt;/strong&gt;. Graph traversals can go sideways very quickly, and once infinite or near-infinite queries start piling up, they can take an entire database down.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;the-save-defensive-engineering-first&quot;&gt;The save: defensive engineering first&lt;/h2&gt;
&lt;p&gt;Here’s the twist: I didn’t deploy the query blindly.&lt;/p&gt;
&lt;p&gt;Two deliberate design decisions saved us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-workspace scoping&lt;/strong&gt;  a misbehaving query was limited to a single tenant.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A hard 15-second query timeout&lt;/strong&gt;  runaway queries couldn’t block the system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of bringing the entire service down, only the offending workspace felt the impact.&lt;/p&gt;
&lt;p&gt;That’s not luck.
And it’s not statistics.&lt;/p&gt;
&lt;p&gt;That’s engineering.&lt;/p&gt;
&lt;p&gt;It’s also the part of the job you only appreciate &lt;em&gt;before&lt;/em&gt; something becomes an incident. This is where engineers design systems; coding is just one slice of the work  usually the one that wrecks your back.&lt;/p&gt;
&lt;p&gt;Even the dopamine doesn’t come from “the code worked”, but from realizing the solution &lt;strong&gt;survived reality&lt;/strong&gt;.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;the-fix-split-simplify-and-respect-index-usage&quot;&gt;The fix: split, simplify, and respect index usage&lt;/h2&gt;
&lt;p&gt;Next, I benchmarked properly.&lt;/p&gt;
&lt;p&gt;I replicated production-like tables, generated mock data, and mirrored production indexes. You don’t need a perfect clone  you need something &lt;strong&gt;close enough&lt;/strong&gt; to reveal how things behave under stress.&lt;/p&gt;
&lt;p&gt;For small datasets, everything looked fine. I pulled median numbers from Databricks to estimate realistic loads.&lt;/p&gt;
&lt;p&gt;Seemed okay.&lt;/p&gt;
&lt;h3 id=&quot;ship-it-yeet&quot;&gt;Ship it, yeet!&lt;/h3&gt;
&lt;p&gt;It worked exactly as expected… for median users.&lt;/p&gt;
&lt;p&gt;Within hours of release, timeout alerts started firing in crowded regions. Heavy users had arrived.&lt;/p&gt;
&lt;p&gt;A feature that returns nothing is not a feature.&lt;/p&gt;
&lt;p&gt;Quick &lt;strong&gt;ANALYZE&lt;/strong&gt;. The problem was obvious: indexes were effectively abandoned. A single recursive CTE with an &lt;code&gt;OR&lt;/code&gt; condition throws away most of what makes relational databases fast.&lt;/p&gt;
&lt;p&gt;What the database was really doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scan A&lt;/li&gt;
&lt;li&gt;scan B&lt;/li&gt;
&lt;li&gt;merge&lt;/li&gt;
&lt;li&gt;deduplicate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I changed the approach.&lt;/p&gt;
&lt;p&gt;Instead of one recursive query handling both directions, I split it into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one traversal for forward edges&lt;/li&gt;
&lt;li&gt;one traversal for reverse edges&lt;/li&gt;
&lt;li&gt;merge and deduplicate &lt;strong&gt;in application code&lt;/strong&gt;, not in the database&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That small refactor turned a query that timed out under load into one that consistently runs in &lt;strong&gt;~30 ms&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Why such a dramatic difference?&lt;/p&gt;
&lt;p&gt;Because in databases, &lt;strong&gt;efficiency isn’t about correctness  it’s about predictable access paths&lt;/strong&gt;.
By separating traversal directions, each query could use its index cleanly. No &lt;code&gt;OR&lt;/code&gt;. No bitmap chaos. No exponential blow-ups.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;what-this-taught-me-and-what-i-want-you-to-take-away&quot;&gt;What this taught me (and what I want you to take away)&lt;/h2&gt;
&lt;p&gt;Three inseparable truths stand out.&lt;/p&gt;
&lt;h3 id=&quot;1-tests-catch-bugs-engineering-catches-failures&quot;&gt;1. Tests catch bugs; engineering catches failures&lt;/h3&gt;
&lt;p&gt;Tests prove logic.
Design protects production.&lt;/p&gt;
&lt;p&gt;Don’t let AI take the wheel entirely.&lt;/p&gt;
&lt;p&gt;AI can help with both  &lt;em&gt;if&lt;/em&gt; you ask the right questions.&lt;/p&gt;
&lt;p&gt;When I wrote the original query, I was testing correctness.
When I rewrote it, I was engineering for failure  with AI as an assistant.&lt;/p&gt;
&lt;p&gt;Those are different goals.&lt;/p&gt;
&lt;hr/&gt;
&lt;h3 id=&quot;2-defensive-defaults-save-your-future-self&quot;&gt;2. Defensive defaults save your future self&lt;/h3&gt;
&lt;p&gt;Timeouts, scoped queries, resource limits  they feel like overhead until the day they save you.&lt;/p&gt;
&lt;p&gt;Treat them not as optional extras, but as &lt;strong&gt;core safety rails&lt;/strong&gt;. Bake them into every AI-assisted session you have.&lt;/p&gt;
&lt;hr/&gt;
&lt;h3 id=&quot;3-mindset-beats-tools&quot;&gt;3. Mindset beats tools&lt;/h3&gt;
&lt;p&gt;You can have the best language, the smartest LLM, the fanciest deployment setup&lt;br/&gt;
but without the habit of asking &lt;em&gt;“what could go wrong here?”&lt;/em&gt;, you’ll eventually pay for it.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;I’m writing this not as a tutorial, but as a reminder   to myself and to anyone building systems for real users at real scale:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Elegance in code doesn’t guarantee stability in production.
Reliability is earned through anticipation, not just validation.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Build with that mindset first, and everything else becomes faster  and more enjoyable  with AI.&lt;/p&gt;
&lt;p&gt;Cheers to building software that survives the real world.&lt;/p&gt;</content:encoded><category>claude</category><category>claude code</category><category>claude sonnet</category><category>claude opus</category><category>TDD</category><category>defensive engineering</category><category>TDD with AI coding</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>AI Coding Agents: Hands-On Test of Codex, Claude CLI &amp; Gemini CLI</title><link>https://omeratagun.net/blog/ai-coding-agents-experiment/</link><guid isPermaLink="true">https://omeratagun.net/blog/ai-coding-agents-experiment/</guid><description>Field notes from running three AI coding agents on two real-world projects.</description><pubDate>Sat, 28 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Hmm… maybe that side project I keep avoiding could write itself?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We’re officially in the age of AI-assisted coding—at least according to the shiny slide decks from every AI CEO on the planet. Over the past few weeks I put that claim to the test, unleashing &lt;strong&gt;Codex Cloud&lt;/strong&gt;, &lt;strong&gt;Claude CLI&lt;/strong&gt;, and the brand-new &lt;strong&gt;Gemini CLI&lt;/strong&gt; on two personal projects. Here’s how that rodeo went.&lt;/p&gt;
&lt;h3 id=&quot;the-test-subjects&quot;&gt;The Test Subjects&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Diagramat&lt;/strong&gt; – a green-field diagram editor à la draw.io, built from scratch in an npm work-spaced monorepo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;eDominations v2&lt;/strong&gt; – a 2017 browser game I mothballed in 2022. Goal: port the backend from Laravel/MySQL to NestJS/Postgres and modernise the whole stack.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;chapter-1-codex&quot;&gt;Chapter 1: Codex&lt;/h2&gt;
&lt;p&gt;Maybe the best one so far. Not because it is the best model out there but because its concurrency capability. You are not locked into your IDE to use CLI tools from other models. As well as, you do not have any safety concerns with the data that can mistakenly be leaked by using terminal commands.&lt;/p&gt;
&lt;p&gt;Initially, I have created a repository to develop diagramming editor.
My idea was to prepare an npm workspaces so that I can split the code into multiple packages and have a monorepo with all the linters, tests.
Prepared all deployment yaml files for k8s cluster and CI/CD pipelines for GitHub Actions.
So instead of locally testing the changes, i could quickly look from my mobile if things go well.
Goal was to eliminate halucination and have a clear structure to work with.
This did work well in the beginning, when I described the project to the codex, it was able to understand and deliver not bad results.
However, it was repetitive and once in a while could get lost and do entirely different things.
I had to be careful about what I was asking and how I was asking it.&lt;/p&gt;
&lt;p&gt;I have figured out that codex works best when you provide AGENTS.md file in the root of your repository. This file should contain the description of the project, its structure, and how to run it.
It should also contain the instructions on how to use the codex. This way, codex can understand the context of the project and provide better results.&lt;/p&gt;
&lt;p&gt;AGENTS.md file i used for this project was like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;```markdown&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Repository Guidelines for Agents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;This repository is a Node.js monorepo managed with **npm workspaces**. Node 22 is required.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;## Project Goal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;This project aims to build a robust, feature-rich diagramming editor similar to [draw.io](https://www.drawio.com). The implementation should closely mirror the usability and key functionalities provided by draw\.io, ensuring a smooth, intuitive user experience.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;## Key Architectural Principles&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;The following principles guide all code contributions and implementations:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### Modularity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* All components should be designed as loosely coupled, reusable modules.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* Ensure each module has clear, minimal dependencies.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* Follow package boundaries clearly defined within the `packages/` directory.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* Design modules as plug-and-play packages so they can be swapped or extended with minimal friction, mirroring draw.io&amp;#39;s scalability.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### SOLID Principles&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;All code should adhere strictly to the SOLID principles:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Single Responsibility Principle (SRP)**: Each module or class should have a single clear purpose.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Open/Closed Principle (OCP)**: Modules should be open for extension but closed for modification.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Liskov Substitution Principle (LSP)**: Derived classes must be substitutable for their base classes without altering correctness.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Interface Segregation Principle (ISP)**: Favor specific interfaces over general-purpose ones.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Dependency Inversion Principle (DIP)**: Depend on abstractions rather than concrete implementations.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### Heavy Testing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* Every new feature or module should come with comprehensive unit tests.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* Integration and end-to-end (E2E) tests are essential, ensuring seamless interactions among components.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* Tests must be clear, readable, and maintainable.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* Aim for high test coverage, particularly for core logic and user interactions.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;## Directory Overview&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* `packages/` – Core libraries and reusable modules following SOLID principles.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* `ui-shell/vanilla/` – Demo UI built with Vite + Tailwind, adhering to modular and testable design.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* `k8s/` – Kubernetes manifests for deployment.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;## Common Commands&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Run these commands from the repository root:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### Install all dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;```sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install --workspaces --include-workspace-root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### Run unit tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;```sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm test&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### Run Playwright E2E tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;```sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm test --workspace e2e&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;This spins up the demo UI on port 5173 and executes all tests located in `e2e/tests`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### Lint all packages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;```sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm run lint&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### Start the demo UI in dev mode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;```sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm run dev --workspace ui-shell/vanilla&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Opens the demo UI at [http://localhost:5173](http://localhost:5173).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### Build the production UI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;```sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm run build --workspace ui-shell/vanilla&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Output is placed in `ui-shell/vanilla/dist`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### Docker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;```sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;docker compose up # builds and serves on port 3002&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### Kubernetes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Manifest files reside in `k8s/`. Apply them using:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;```sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;kubectl apply -f k8s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;## Development Guidelines for Agents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Clearly defined tasks:** Implement tasks in small, incremental changes, clearly describing input and output expectations.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Code readability:** Write clean, well-documented code. Clearly comment any complex logic or design decisions.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Automated feedback loops:** Regularly run linting, formatting, and tests to ensure high-quality code.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Lint checks:** Ensure `npm run lint` passes without any warnings or errors before committing.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Continuous integration:** Use automated CI/CD pipelines to validate code quickly and reliably.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Meaningful abstractions:** Whenever you implement logic, extract it into well-named, reusable functions. Favor small, standalone functions to encourage composability.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Minimize conditionals:** Avoid large `if`/`else` blocks. Prefer early returns or design patterns such as strategy or polymorphism to keep flow control clear and modular.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* **Feature visibility:** Every new feature must also be enabled in the demo UI so a human tester can manually exercise it. Avoid implementing functionality that cannot be accessed through the interface.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;## Notes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* ESLint runs automatically upon commit via Husky, enforcing code quality.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* Refer to the `README.md` for comprehensive details on features and repository layout.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After couple of iterations, codex new what to do and how to do it.&lt;/p&gt;
&lt;p&gt;In the end of the 700+ commits done by codex, i had a working prototype of a diagramming editor that was able to render diagrams and export them as images.
It was not perfect, but it was a good start. In overall it was a good experience.&lt;/p&gt;
&lt;p&gt;You can read more about the project &lt;a href=&quot;https://diagramat.omeratagun.net&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-full&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://omeratagun.net/static/images/diagramat.png&quot; alt=&quot;Rancherk3s&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;chapter-2--codex--claude-cli-tag-team&quot;&gt;Chapter 2 — Codex + Claude CLI Tag Team&lt;/h2&gt;
&lt;p&gt;Back in 2022, i had started v2 for eDominations which is a browser game that i had published its first version in 2017.
Before i stopped working on v2 i had a working version missing some features.
Written in React as SPA, laravel as backend and mysql as database. All you had to do was docker compose up and you were ready to go.
Wanted to test the coding agents with this project if they can nicely and quickly port it to a new stack. Goal was rewriting from Laravel into NestJS and MySQL into Postgres.&lt;/p&gt;
&lt;p&gt;Now that i had experience with codex, i wanted to see if it thrives with a more complex project. Well, in the beginning started well. Codex was able to understand the project since i provided AGENTS.md rather first commit.
This time we were on stereoids with Codex. First i made sure it analyzed the project when i asked a task to do and come up with a plan and a task list.
This way, i was able to see if it was on the right track or not. If it was not, i could correct it and ask to do it again.
After couple of days, codex provided 4x concurrency to the codex cloud. Hence, i got even faster results. However, it was too delusional no matter what i did.
Lesser descriptions, more comments, more instructions, it was not able to deliver what i wanted. Simply i tried everything i could think of.&lt;/p&gt;
&lt;p&gt;Dont take this project lightly, in a single repository, there were client, legacy service, new service, 2 additional microservices for cdn proxy and socket service for live chat. Not an easy with limited context length.&lt;/p&gt;
&lt;p&gt;AGENTS.md file i used for this project was like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;# Repository Guidelines for Agents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;This repository is a browser game called eDominations includes couple of services.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;## Project Goal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;This project aims to build an amazing and entertaining browser based multiplayer strategy game with robust, feature-rich backend services. The game is designed to be modular, scalable, and maintainable, allowing for easy addition of new features and enhancements over time.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Short term goal is to rewrite the PHP application into NestJS in &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`service-nest`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; and migrate the database from MySQL to PostgreSQL.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;## Key Architectural Principles&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;The following principles guide all code contributions and implementations:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;### Modularity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; All components should be designed as loosely coupled, reusable modules.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Ensure each module has clear, minimal dependencies.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Design modules as plug-and-play packages so they can be swapped or extended with minimal friction.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;### SOLID Principles&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;All code should adhere strictly to the SOLID principles:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **Single Responsibility Principle (SRP)**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Each module or class should have a single clear purpose.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **Open/Closed Principle (OCP)**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Modules should be open for extension but closed for modification.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **Liskov Substitution Principle (LSP)**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Derived classes must be substitutable for their base classes without altering correctness.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **Interface Segregation Principle (ISP)**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Favor specific interfaces over general-purpose ones.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **Dependency Inversion Principle (DIP)**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Depend on abstractions rather than concrete implementations.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;### Heavy Testing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Every new feature or module should come with comprehensive unit tests.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Integration and end-to-end (E2E) tests are essential, ensuring seamless interactions among components.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Tests must be clear, readable, and maintainable.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Aim for high test coverage, particularly for core logic and user interactions.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;## Directory Overview&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; `client/`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; – React client application. Face of the game, built with TypeScript and React.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; `service/`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; – Laravel application, the original backend of the game, slated to be replaced by the NestJS implementation in &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`service-nest`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; `service-nest/`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; – NestJS backend hosting modular microservices, including the WebSocket service.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; `service-nest/src/microservices/cdn/`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; – standalone CDN microservice for serving game assets.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; `service-nest/src/microservices/socket/`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; – NestJS microservice responsible for WebSocket communication.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; `k8s/`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; – Kubernetes manifests for deployment.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; `docs/`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; – central documentation hub for design notes, API references, and migration guides.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;## Common Commands&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Run these commands from the repository root:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;### Docker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;```sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; up&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### service-nest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run build`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; before&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; executing&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tasks&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;service-nest&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run start`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NestJS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; whenever&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; you&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; on&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; features&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; or&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; debugging&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tasks.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;## Development Guidelines for Agents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Clearly defined tasks:&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Implement tasks in small, incremental changes, clearly describing input and output expectations.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Code readability:&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Write clean, well-documented code. Clearly comment any complex logic or design decisions.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Automated feedback loops:&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Regularly run linting, formatting, and tests to ensure high-quality code.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Lint checks:&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Ensure &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run lint`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; passes&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; without&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warnings&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; or&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; errors&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; before&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; committing.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Continuous integration:&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Use automated CI/CD pipelines to validate code quickly and reliably.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Meaningful abstractions:&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Whenever you implement logic, extract it into well-named, reusable functions. Favor small, standalone functions to encourage composability.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Minimize conditionals:&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Avoid large &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`if`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`else`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; blocks.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Prefer&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; early&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; returns&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; or&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; design&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; patterns&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; such&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; as&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; strategy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; or&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; polymorphism&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; keep&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; flow&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; control&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; clear&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; modular.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Feature visibility:&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Every new feature must also be enabled in the demo UI so a human tester can manually exercise it. Avoid implementing functionality that cannot be accessed through the interface.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No result excited me. Thats when i decided to try out Claude CLI. I had a good experience with it in the past, so i thought it would be a good idea to give it a try.
Well when you initiate your claude cli, you can &lt;code&gt;/init&lt;/code&gt; and let it do its job and prepare a nice AGENTS.md alike file for you. Same purpose as AGENTS.md. However this time i let it cook. So it did a nice meal for itself.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;# CLAUDE.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;## Project Overview&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;eDominations v2 is a browser-based multiplayer strategy game built as a monorepo with microservices architecture. The project is migrating from a PHP/Laravel backend to NestJS with PostgreSQL.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;## Architecture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;### Core Services&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **client/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: React TypeScript SPA for the game interface&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **service-nest/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: NestJS backend with domain-driven design, including:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Main API service on port 3001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; CDN microservice on port 3036 (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`service-nest/src/microservices/cdn/`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; WebSocket microservice on port 3035 (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`service-nest/src/microservices/socket/`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **service/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Legacy Laravel backend (being phased out)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **k8s/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Kubernetes deployment manifests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **docs/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Project documentation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;### Domain Structure&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;The NestJS service follows domain-driven design with domains in &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`service-nest/src/domains/`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **user/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: User management, profiles, training, travel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **battles/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Combat system, battlehits, wars&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **country/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Countries, regions, politics, economics&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **company/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Business system, employees, market&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **journalism/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Articles, newspapers, comments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **alliance/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Player alliances and diplomacy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **energy/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Energy system and logging&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **elections/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Political elections and voting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **chat/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: In-game messaging and chat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8;font-weight:bold&quot;&gt; **cron/**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Scheduled tasks and game events&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;## Development Commands&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF;font-weight:bold&quot;&gt;### Docker Development&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;```bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Start all services&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; up&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Start specific services&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; up&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; service-nest&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; postgres&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### service-nest (NestJS Backend)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;```&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; service-nest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Build (required before most tasks)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Development&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run start:dev          &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Main API with hot reload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run start:cdn          &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# CDN microservice&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run start:socket       &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# WebSocket microservice&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Testing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run test               &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Unit tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run test:e2e           &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# End-to-end tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run test:cov           &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Coverage report&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Linting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run lint               &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Auto-fix linting issues&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run lint:ci            &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Check linting (CI mode)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Database&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run migration:run      &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Run migrations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run migration:revert   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Revert last migration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run seed               &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Seed database&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### client (React Frontend)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;```&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Development&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dev&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                # Start with Tailwind compilation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                  # React dev server only&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tailwind&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;           # Tailwind CSS compilation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Testing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; test&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;               # Unit tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; e2e&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                # Cypress e2e tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dev:cy&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;             # Cypress interactive mode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Linting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lint&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;               # ESLint check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lint:fix&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;           # Auto-fix linting issues&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; format&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;             # Prettier formatting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;              # Production build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;## Database &amp;amp; Infrastructure&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### PostgreSQL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Host&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; postgres&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (in &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Docker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) or localhost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Port&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5432&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Database&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; edominations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Admin&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; UI&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pgAdmin&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; on&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; port&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5050&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (admin@admin.com &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; admin&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### Redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Host&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; redis&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (in &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Docker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) or localhost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Port&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 6379&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Used&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; caching&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; WebSocket&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; scaling&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;## Testing Strategy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### Unit Tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Located&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; alongside&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; source&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; files&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;__tests__/&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; directories&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NestJS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; uses&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Jest&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; TypeORM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; test&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; utilities&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; React&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; uses&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Testing&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Library&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Jest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### E2E Tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NestJS:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;service-nest/test/*.e2e-spec.ts&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; using&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Testcontainers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; React:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;client/cypress/e2e/&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; using&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Cypress&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### Key Test Patterns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Use&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;beforeEach&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; clean database state in e2e tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Mock&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; external&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dependencies&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unit&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Test&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; both&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; success&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; scenarios&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; integration&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tests&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; complex&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; domain&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; interactions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;## Code Conventions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### NestJS Backend&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Use&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; domain-driven&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; design&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; modules&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; per&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; business&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; domain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Controllers&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; handle&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; HTTP&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; requests,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; services&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; contain&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; business&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; logic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; TypeORM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; entities&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; proper&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; relationships&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; indexes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; DTOs&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; request/response&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; validation&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; using&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; class-validator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Comprehensive&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; handling&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; proper&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; HTTP&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; status&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; codes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### React Frontend&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; TypeScript&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; strict&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; typing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Functional&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; components&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hooks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Tailwind&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CSS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; styling&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Context&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; global&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; state&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; management&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Component&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; colocation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (components &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;near&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; their&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; usage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;## Development Workflow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Before&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; starting&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run build`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; in &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;service-nest/&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Testing&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Always&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tests&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; before&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; committing&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Linting&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Use&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run lint`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; check&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; code&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; quality&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;4.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Database&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; changes&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Create&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; migrations&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; schema&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;5.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Feature&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; development&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Follow&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; domain&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; boundaries&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; when&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; adding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; functionality&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;## Migration Context&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;The&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; project&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; actively&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; migrating&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Laravel/MySQL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NestJS/PostgreSQL.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; When&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; working&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; on&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; features:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Prefer&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; implementing&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; functionality&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;service-nest/&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Database&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; schema&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; defined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; TypeORM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; migrations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Legacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Laravel&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; code&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;service/&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; only&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Game&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; logic&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; should&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; be&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; implemented&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; appropriate&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; domain&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; modules&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;## Environment Configuration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### Docker Environment Files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;client/.env.docker&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Client&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; configuration&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Docker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;service-nest/.env&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Main&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NestJS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; service&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;service-nest/.env.socket&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; WebSocket&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; service&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;service-nest/.env.cdn&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CDN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; service&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;### Key Environment Variables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DB_HOST&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DB_PORT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DB_USERNAME&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DB_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DB_DATABASE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PostgreSQL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; connection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;REDIS_HOST&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;REDIS_PORT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Redis&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; connection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;JWT_SECRET&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;JWT_EXPIRES_IN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Authentication&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; configuration&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Did a great job. Finally i got some accurae results and felt that tool got me covered. Only the problem that damage by Codex was done a little.
Even though i had over 600 tests in total with e2e tests. Unit tests were rather testing framework itself than actually testing something :).
My poor M1 macbook pro started burning and heating up like a furnace.
Its summer, i dont need furnace.&lt;/p&gt;
&lt;h3 id=&quot;aaand-finally-i-figured-out-that-i-have-no-clue-what-is-going-on-and-nothing-works&quot;&gt;Aaand finally i figured out that i have no clue what is going on and nothing works.&lt;/h3&gt;
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-full&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://i.kym-cdn.com/entries/icons/facebook/000/008/342/ihave.jpg&quot; alt=&quot;i have no idea what i am doing&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;third-chapter-the-whole-zoo-gemini-cli--claude-cli--codex--human&quot;&gt;Third chapter: The Whole Zoo: Gemini CLI × Claude CLI × Codex + Human&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Why not staff a full AI scrum team?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Spinning 2 clones of the repo—one per agent—worked technically, but juggling PRs felt like herding caffeinated cats.&lt;/p&gt;
&lt;p&gt;After all the pain and suffering, i decided to give it a try with human review. Don’t get me wrong, i did review previously but whenever a sets of tasks was completed. Not like every PR careful review. Whenever i asked claude to do something, i would review the code and see if it was on the right track or not. Manual testing alongside e2e tests, like a primate i was clicking here and there to see if things were working as expected.
Claude was way more impressive than codex. It did truly felt like understood but the limitation with entry package was a problem. Right at the moment i am about to complete the task with Claude, it was like “Nope, not today”. You have got to wait for some amount of time, chill your horses and then come back to me.&lt;/p&gt;
&lt;h2 id=&quot;fourth-chapter-gemini-cli--claude-cli--codex--human-review&quot;&gt;Fourth chapter: Gemini CLI + Claude CLI + Codex + Human review&lt;/h2&gt;
&lt;p&gt;I did started the question myself “Why not use all of them together?”. Since Codex handles all its operations on the cloud all i was to clone repository twice and spawn gemini and cloud in each clone of the same repository.
All of a sudden, i was leading a team of 3 AI agents and myself. I was the product owner, codex was the developer, gemini was the tester and claude was the reviewer. Joking, they did all work i was just clicking on merge.&lt;/p&gt;
&lt;p&gt;After couple of days of struggle, i don’t think i ever felt this bad that i am so disconnected, battling with 3 AI agents to get the job done
while i had no clue of the project’s latest status.&lt;/p&gt;
&lt;h2 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AI agents ≠ architects. Give them a blueprint; don’t ask them to design the house.&lt;/li&gt;
&lt;li&gt;Supervision is non-negotiable. Unshepherded agents will drift off-spec.&lt;/li&gt;
&lt;li&gt;Generate tests, then audit them. Half their assertions checked nothing useful.&lt;/li&gt;
&lt;li&gt;Boilerplate? Perfect use-case. CI configs, CRUD layers, doc scaffolding—let the bots grind.&lt;/li&gt;
&lt;li&gt;Small, isolated functions shine. The narrower the task, the crisper the output.&lt;/li&gt;
&lt;li&gt;Stay in the loop. If you stop reading the code, you stop knowing your project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most importantly, knowing the project is how we find comfort and knowledge day by day ( focus, discipline e.g ). If you let bot do all the work, highly likely you will instruct it wrong eventually.&lt;/p&gt;</content:encoded><category>codex</category><category>claude cli</category><category>gemini cli</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>5 days is enough</title><link>https://omeratagun.net/blog/5-days-enough/</link><guid isPermaLink="true">https://omeratagun.net/blog/5-days-enough/</guid><description>From Pull Requests to Smart Blinds: A Week of Building with AI and Automation. Just because I had to be at home.</description><pubDate>Fri, 09 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I had a trip planned. Calendar cleared, bags half-packed. Then life did its thing and plans fell through. Instead of sulking, I pivoted. One thought kept echoing:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Well, I guess I could build that thing I’ve been putting off…”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That thought snowballed into one of the most productive weeks I’ve had in a long time. I ended up building a GitHub App that reviews PRs with AI, extended it into a local-first personal assistant, and wired up my house to respond to presence like a proper smart system.&lt;/p&gt;
&lt;p&gt;Here’s the log. You can find it &lt;a href=&quot;https://softastik.com&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;1-lgtm-reviewer-a-github-app-for-ai-powered-code-reviews&quot;&gt;1. LGTM Reviewer: A GitHub App for AI-Powered Code Reviews&lt;/h2&gt;
&lt;p&gt;This idea had been sitting in my head for months: what if a bot could take the chore out of code reviews? Not just another CI check. Something that &lt;em&gt;reads&lt;/em&gt; the code changes and leaves human-ish feedback—like a real teammate would.&lt;/p&gt;
&lt;p&gt;So I built it. I’m not the first, of course, but I built it my way.&lt;/p&gt;
&lt;h3 id=&quot;the-stack&quot;&gt;The stack:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go + Gin&lt;/strong&gt;: Fast, simple, clean HTTP framework. Handles GitHub webhook routing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;go-github&lt;/strong&gt;: Official GitHub API client for Go.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Postgres&lt;/strong&gt;: Stores background jobs triggered by pull request events.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ollama + Open WebUI&lt;/strong&gt;: Local LLM stack, no OpenAI, no cloud APIs. Total privacy. Running on an RTX 4060 with CodeLlama 7B—it’s decent, not magical.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-workflow&quot;&gt;The workflow:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;GitHub sends a webhook when a PR is opened.&lt;/li&gt;
&lt;li&gt;The app queues it in a &lt;code&gt;jobs&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;A background worker picks it up and parses the diff.&lt;/li&gt;
&lt;li&gt;I built a custom patch parser that extracts &lt;em&gt;just&lt;/em&gt; the meaningful code edits—no noise.&lt;/li&gt;
&lt;li&gt;That context is used to craft a prompt for the LLM.&lt;/li&gt;
&lt;li&gt;The LLM generates concise, focused review comments.&lt;/li&gt;
&lt;li&gt;The app posts those comments inline on the PR.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first version was overly verbose. Then too vague. After iterating on both the patch parsing and prompt structure, I got something closer to what I’d write myself.&lt;/p&gt;
&lt;p&gt;Even now, I’m still not 100% satisfied—home-run AI reviews require better models. But for a local setup, it’s a surprisingly workable baseline.&lt;/p&gt;
&lt;h3 id=&quot;bonus-a-ui-for-sanity&quot;&gt;Bonus: A UI for sanity&lt;/h3&gt;
&lt;p&gt;To keep things visible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;React + Tailwind + Vite&lt;/strong&gt;: Stood up in under an hour.&lt;/li&gt;
&lt;li&gt;Shows job queue and review results.&lt;/li&gt;
&lt;li&gt;Not flashy, just enough to debug and monitor the pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;2-from-code-reviews-to-chore-reviews-a-personal-ai-assistant&quot;&gt;2. From Code Reviews to Chore Reviews: A Personal AI Assistant&lt;/h2&gt;
&lt;p&gt;Once the job system and LLM prompt pipeline were solid, a new idea clicked: &lt;em&gt;Why limit this to code?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So I started reusing the same infra to manage &lt;em&gt;me&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I extended the app to accept inputs from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Apple calendar &amp;amp; reminders&lt;/strong&gt; (via iCloud sync tools)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step counts and location from the Home Assistant mobile app&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device and sensor states from Home Assistant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manual jobs from iOS Shortcuts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bi-directional communication between Apple Shortcuts and HA, routed via Google Nest&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Suddenly the assistant had some situational awareness. It knows when I’m home. It knows which room I’m in (thanks, FP2). It knows my schedule. It could (in theory) nudge me or help me plan.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“You said you’d clean the kitchen by tonight. You’re still in the office. Want me to queue a reminder for tomorrow morning?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s the dream, anyway. Reality: it doesn’t manage much yet—frankly, it can’t even manage &lt;em&gt;itself&lt;/em&gt;. But the scaffolding is there. The &lt;code&gt;jobs&lt;/code&gt; table and LLM worker model make it easy to add these use cases with minimal overhead.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;3-smart-home-real-sensors-real-automations&quot;&gt;3. Smart Home: Real Sensors, Real Automations&lt;/h2&gt;
&lt;p&gt;Home automation is a rabbit hole I fall into every few months. This week, I went deep.&lt;/p&gt;
&lt;h3 id=&quot;aqara-fp2-presence-not-just-motion&quot;&gt;Aqara FP2: Presence, not just motion&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;Aqara FP2&lt;/strong&gt; uses mmWave to detect &lt;em&gt;presence&lt;/em&gt;, not just motion. That means it knows when I’m sitting still at my desk or lying on the couch—not just when I move.&lt;/p&gt;
&lt;p&gt;That granularity changes everything.&lt;/p&gt;
&lt;p&gt;Example: if I leave a room, the lights turn off after 30 seconds. But if I’m sitting still? They stay on. No more waving my arms like a confused NPC just to keep the lights alive.&lt;/p&gt;
&lt;h3 id=&quot;home-assistant-full-migration-to-rpi-4&quot;&gt;Home Assistant: Full Migration to RPi 4&lt;/h3&gt;
&lt;p&gt;Migrated everything to a Raspberry Pi 4 with 8GB RAM, running Home Assistant OS.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native Matter support? ✔️&lt;/li&gt;
&lt;li&gt;No Docker hacks? ✔️&lt;/li&gt;
&lt;li&gt;Easy backups and recovery? ✔️&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Honestly, it’s the most user-friendly it’s ever been. Almost boring how well it works.&lt;/p&gt;
&lt;h3 id=&quot;elero-comfort-868-blinds-the-final-boss&quot;&gt;Elero Comfort-868 Blinds: The Final Boss&lt;/h3&gt;
&lt;p&gt;I have some lovely electric blinds that speak a proprietary 868 MHz protocol. Fancy, but dumb.&lt;/p&gt;
&lt;p&gt;I’m working on wiring them into Home Assistant—whether that’s via USB bridge, ESPHome, or straight-up reverse engineering.&lt;/p&gt;
&lt;p&gt;The goal: have the blinds close automatically when I leave the room &lt;em&gt;and&lt;/em&gt; the sun is blasting through the windows. Or maybe just when it’s 3PM and I want to watch TV.&lt;/p&gt;
&lt;p&gt;Still experimenting with serial sniffers and module options. It’s a fun little boss fight.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;4-thoughts-and-takeaways&quot;&gt;4. Thoughts and Takeaways&lt;/h2&gt;
&lt;p&gt;This week, one project turned into five—but everything looped back to a central theme:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What if software actually worked &lt;em&gt;for me&lt;/em&gt;—in code and in life?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;what-stood-out&quot;&gt;What stood out:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reusing architecture saved time&lt;/strong&gt;: The background worker + job queue + LLM pipeline became the backbone for multiple ideas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local AI is viable, kinda&lt;/strong&gt;: Running models like CodeLlama and Mistral locally gave fast, private, “meh-enough” results. Not perfect, but way more usable than I expected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The smart home feels smart now&lt;/strong&gt;: With mmWave sensors, local logic, and real-time context, it responds &lt;em&gt;to me&lt;/em&gt;, not the other way around. No more shouting room names and commands like a voice butler.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping It Up&lt;/h2&gt;
&lt;p&gt;What’s next? I’m planning to shift my personal AI strategy toward RAG (Retrieval-Augmented Generation), since fine-tuning isn’t viable for a personal project at this scale. That said, long-context support still feels just out of reach—hoping better models land soon.&lt;/p&gt;
&lt;p&gt;Also: building things in Go was so refreshing. I’ll be using it way more from here on.&lt;/p&gt;</content:encoded><category>github app</category><category>ai code review</category><category>golang</category><category>react</category><category>tailwindcss</category><category>home assistant</category><category>smart home</category><category>llm</category><category>ollama</category><category>cloudflare tunnel</category><category>aqara fp2</category><category>elero blinds</category><category>docker</category><category>webhooks</category><category>vite</category><category>blog</category><category>local ai</category><category>presence sensors</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Home k8s cluster</title><link>https://omeratagun.net/blog/home-k8s-cluster/</link><guid isPermaLink="true">https://omeratagun.net/blog/home-k8s-cluster/</guid><description>How to overkill a home server</description><pubDate>Fri, 02 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It was quite a while that I had no time to write any posts. Eventually, I got the time due to having my vacation cancelled and having to stay at home with a bunch of PTO in hand. So, it was time.&lt;/p&gt;
&lt;h2 id=&quot;overkilling-home-server&quot;&gt;OVERKILLING HOME SERVER&lt;/h2&gt;
&lt;p&gt;I had this machine that I got back then with an i5 12400f and 4060 and 47GB RAM with 1tb nvme and 1tb usb ssd.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My plan:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create an organization in GitHub for home projects.&lt;/li&gt;
&lt;li&gt;Run my own GitHub runners within a cluster using Arc Runners.&lt;/li&gt;
&lt;li&gt;Include a kubeconfig in the repositories so that I could create simple deployment and service YAML files to deploy.&lt;/li&gt;
&lt;li&gt;Have local AI running within the cluster, so NVIDIA support in pods was needed for the job.&lt;/li&gt;
&lt;li&gt;Plex server.&lt;/li&gt;
&lt;li&gt;This blog itself – a new project that I am building with Go.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, I have started going with Proxmox so I could have 3 nodes from the same machine and extend furthermore,
but that was quite annoying to handle all virtualization for really no gains (I have other machines as well).
I leveraged its simplicity to dump a virtual machine and create a new one right after.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;I use cloudflare zero tunnel to expose my cluster and other applications to my domains. If you are not intended to do that, this story/tutorial is not for you.
You can read more details &lt;a href=&quot;https://omeratagun.net/blog/home-server-with-cloudflare&quot;&gt;here&lt;/a&gt;. I also do not mention how ingress or services or k8s itself work within this blog post, you got to figure that out yourself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First steps:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With Proxmox installed, to refresh my memories I started creating my cluster using Rancher. I had to get back to thinking on YAML world. So, I broke entire thing couple of times. Finally, I had a working prototype with Arc Runners and deployments from repositories. Yay!
Fresh bare Ubuntu installation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second step:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Rancher changed its tutorial a bit after couple of years. So i have gone with &lt;a href=&quot;https://ranchermanager.docs.rancher.com/getting-started/quick-start-guides/deploy-rancher-manager/helm-cli&quot;&gt;Helm CLI installation&lt;/a&gt;. Quite straightforward, all you have to go to get latest versions from corresponding repositores releases and you are pretty much done.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Third step:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Start with &lt;a href=&quot;https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/quickstart-for-actions-runner-controller&quot;&gt;Actions runner controller&lt;/a&gt; tutorial up until step “Configure a runner scale set”. You need more than what is given to you
In tutorial you will see an installation for arc runners like this;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;INSTALLATION_NAME=&amp;quot;arc-runner-set&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;NAMESPACE=&amp;quot;arc-runners&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GITHUB_CONFIG_URL=&amp;quot;https://github.com/&amp;lt;your_enterprise/org/repo&amp;gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GITHUB_PAT=&amp;quot;&amp;lt;PAT&amp;gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;helm install &amp;quot;${INSTALLATION_NAME}&amp;quot; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    --namespace &amp;quot;${NAMESPACE}&amp;quot; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    --create-namespace \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    --set githubConfigUrl=&amp;quot;${GITHUB_CONFIG_URL}&amp;quot; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    --set githubConfigSecret.github_token=&amp;quot;${GITHUB_PAT}&amp;quot; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this on its own is not sufficient enough to run docker in docker in which you need most of the time for your CI/CD. After reading the docs, you can find out which keys you need to populate to make that happen&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  --set resourceMeta.noPermissionServiceAccount.labels.app=dummy \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  --set resourceMeta.noPermissionServiceAccount.annotations.foo=bar \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  --set runnerContainerPrivileged=true \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  --set containerMode.type=dind \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  --set dockerEnabled=true \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  --set dind.privileged=true \&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then follow up rest of the tutorial as it is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Forth step:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now we have arc-runners on but they have not any secrets that they can pull images from your repository registry. We need to create one. Create a secret in the same namespace and name it &lt;code&gt;ghcr-secret&lt;/code&gt;. Domain will be &lt;code&gt;ghcr.io&lt;/code&gt; and your username and token with package access.&lt;/p&gt;
&lt;p&gt;You will also need to inject your kubeconfig into your repository action secrets so you can actually deploy into clusters.&lt;/p&gt;
&lt;p&gt;my workflow ci.yaml looks like this&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name: Deploy to production cluster&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;on:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  push:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    branches:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - master&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  workflow_dispatch:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;jobs:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  deploy:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    runs-on: arc-runner-set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if: ${{ github.event_name == &amp;#39;push&amp;#39; || github.event_name == &amp;#39;workflow_dispatch&amp;#39; }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    permissions:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      contents: read&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      packages: write&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    steps:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - name: Checkout repository&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        uses: actions/checkout@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          fetch-depth: 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - name: Log in to GitHub Container Registry&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        uses: docker/login-action@v3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          registry: ghcr.io&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          username: ${{ github.actor }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          password: ${{ secrets.GHCR_PAT }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - name: Get commit count&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        id: vars&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        run: echo &amp;quot;count=$(git rev-list --count HEAD)&amp;quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - name: Build and Push Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        run: |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          cp .env.example .env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          VERSION=${{ steps.vars.outputs.count }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          IMAGE=ghcr.io/${{ github.repository_owner }}/$(basename ${{ github.repository }})/service:$VERSION&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          docker build -t $IMAGE .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          docker push $IMAGE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - name: Pull and deploy Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        run: |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          VERSION=${{ steps.vars.outputs.count }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          IMAGE=ghcr.io/${{ github.repository_owner }}/$(basename ${{ github.repository }})/service:$VERSION&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          docker pull $IMAGE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - name: Set up kubectl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        uses: azure/setup-kubectl@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        id: install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          version: &amp;quot;v1.28.2&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - name: Update Kubernetes Deployment with new image version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        run: |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          VERSION=${{ steps.vars.outputs.count }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          IMAGE=ghcr.io/${{ github.repository_owner }}/$(basename ${{ github.repository }})/service:$VERSION&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          sed -i &amp;quot;s|image: .*|image: $IMAGE|&amp;quot; deployment.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        working-directory: k8s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - name: Apply Kubernetes manifests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        run: |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          kubectl apply -f deployment.yaml --insecure-skip-tls-verify&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          kubectl apply -f service.yaml --insecure-skip-tls-verify&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        working-directory: k8s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Congratulations, now you have a arc runners for your repositories which can spawn a pod, run your jobs and clean all the leftover data after pod is shutdown. I know you don’t need it but its fun!&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;now-we-need-to-deploy-something&quot;&gt;Now we need to deploy something&lt;/h2&gt;
&lt;h3 id=&quot;open-web-ui&quot;&gt;Open web ui&lt;/h3&gt;
&lt;p&gt;Create this as webui.yaml&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;apiVersion: v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;kind: Namespace&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;metadata:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  name: open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;apiVersion: v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;kind: PersistentVolumeClaim&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;metadata:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  name: open-webui-pvc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  namespace: open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;spec:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  accessModes:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    - ReadWriteOnce&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  resources:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    requests:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      storage: 1Gi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;apiVersion: apps/v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;kind: Deployment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;metadata:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  name: open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  namespace: open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;spec:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  replicas: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  selector:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    matchLabels:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      app: open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  template:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    metadata:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      labels:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app: open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    spec:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      containers:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        - name: open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          image: ghcr.io/open-webui/open-webui:main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ports:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            - containerPort: 8080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          volumeMounts:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            - name: data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              mountPath: /app/backend/data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          env:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            - name: WEBUI_PORT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              value: &amp;quot;8080&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      dnsPolicy: ClusterFirstWithHostNet # this is needed for accessing ollama instance in the machine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      volumes:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        - name: data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          persistentVolumeClaim:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            claimName: open-webui-pvc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;apiVersion: v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;kind: Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;metadata:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  name: open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  namespace: open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;spec:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  selector:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    app: open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ports:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    - port: 80&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      targetPort: 8080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      protocol: TCP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  type: ClusterIP&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;kubectl apply -f webui.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and you are done. Create an ingress for where you would like to publish. If you have ollama installed in different machine, you can set that up within open web ui configuration. If ollama will run in same machine as cluster, then make sure ollama environment set for 0.0.0.0 otherwise will be closed to network and open only to localhost.&lt;/p&gt;
&lt;h2 id=&quot;handling-nvidia-support&quot;&gt;Handling nvidia support&lt;/h2&gt;
&lt;p&gt;Now i used nvidia gpu for plex server since i needed more for that than ollama ( you can leverage ollama from machine rather pod ).&lt;/p&gt;
&lt;p&gt;At the end my cluster looked like this.&lt;/p&gt;
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-2/2&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://omeratagun.net/static/images/deployments.png&quot; alt=&quot;Deployments&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-2/2&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://omeratagun.net/static/images/cluster.png&quot; alt=&quot;Cluster&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That sums up! this blog post was meant to be warm up after a very long time. Hopefully i will do better next time.&lt;/p&gt;</content:encoded><category>blog</category><category>k8s</category><category>self hosted</category><category>home server</category><category>rancher</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Testing with chatgpt</title><link>https://omeratagun.net/blog/testing-with-chatgpt/</link><guid isPermaLink="true">https://omeratagun.net/blog/testing-with-chatgpt/</guid><description>How to write unit tests with a help of a bot</description><pubDate>Wed, 29 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You have heard a lot about how chatgpt boosts productivity, yes it does and takes away the boredom of repetitive but necessary tasks.
Testing is a major aspect in software engineering. Helps your pairs/other engineers to understand the specifications and capabilities of the solution provided by you.
It helps speed up adjustment process when a requirements also change. Long story short, this is about how i have leveraged chatgpt to write unit tests in a project that did not have any. Even though i had no clue what the project was about.&lt;/p&gt;
&lt;h2 id=&quot;understanding-the-code&quot;&gt;Understanding the code&lt;/h2&gt;
&lt;p&gt;At some point in your career, you learn empathy is the key to understand somebody elses solution. Yet, there are days you may need some help or you may need to ask some questions about the original solution.
Well, no more. Chatgpt can help you out with the intention. May get the entire picture wrong but remember, it is all about predicting not certainity. As like empathy.&lt;/p&gt;
&lt;p&gt;Lets go through some examples of how can we leverage chatgpt doing this task. Providing example below;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hidePrivateProperties&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;N&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  object&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  properties&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Omit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  properties.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    delete&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; object[property]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; object&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some utility method that helps deleting properties of a given object and returns the same and altered(no copy) object with new type type that omits those properties from the given object.&lt;/p&gt;
&lt;p&gt;Lets ask chatgpt what this does.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Q: Can you tell me what this exactly does?&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;In summary, this function takes an object and an array of properties,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;and it removes those properties from the object.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;The types are designed to ensure that the properties&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;being hidden are actually properties of the object.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Note that using delete on an object modifies it in place, and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;the modified object is then returned.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;FYI i kept entire description hidden and copied only the summary. So now we know that this function does not copy the object, it alters and returns the original object.&lt;/p&gt;
&lt;p&gt;Lets ask chatGPT to write unit tests for this utility function and do some mocks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Q: Can you write me unit tests in jest, also provide some mocks. I have jest environment set already, so skip that part.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Import the function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { hidePrivateProperties } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;./your-file&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Mock data for testing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TestData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  age&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  address&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; mockObject&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TestData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;John Doe&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  age: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;25&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  address: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;123 Main St&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;describe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;hidePrivateProperties&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  it&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;should hide specified properties from the object&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Arrange&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; propertiesToHide&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;age&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;address&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Act&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hidePrivateProperties&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mockObject, propertiesToHide)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Assert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toEqual&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;John Doe&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  it&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;should handle an empty array of properties&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Arrange&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; propertiesToHide&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Act&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hidePrivateProperties&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mockObject, propertiesToHide)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Assert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toEqual&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mockObject) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// The object should remain unchanged&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  it&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;should handle properties that do not exist on the object&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Arrange&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; propertiesToHide&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;nonexistentProperty&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Act&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hidePrivateProperties&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mockObject, propertiesToHide)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Assert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toEqual&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mockObject) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// The object should remain unchanged&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  it&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;should handle an empty object&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Arrange&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; emptyObject&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TestData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Act&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hidePrivateProperties&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(emptyObject, [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;property1&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;property2&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Assert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toEqual&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({}) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// The object should remain empty&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // You can add more test cases based on your specific use cases&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty good right? chatgpt understood the probable scenarios, provided tests for it and let you implement. So, i heard you are saying that you could also do this or even have done it before, why this blog post now?&lt;/p&gt;
&lt;p&gt;Well, we are not done yet. Nice thing about bots that they may see better than you most of times with given enough of context.&lt;/p&gt;
&lt;p&gt;Q: Do you think we may have an edge case in this utility function? if so, could you write me a tests for that?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;describe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;hidePrivateProperties&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // ... other test cases ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  it&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;should handle a null object&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Arrange&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nullObject&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TestData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; propertiesToHide&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;age&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;address&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Act&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hidePrivateProperties&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(nullObject&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, propertiesToHide)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Assert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toBeNull&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// The result should also be null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  it&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;should handle an undefined object&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Arrange&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; undefinedObject&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TestData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; undefined&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; propertiesToHide&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;age&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;address&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Act&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hidePrivateProperties&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(undefinedObject&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, propertiesToHide)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Assert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toBeUndefined&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// The result should also be undefined&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when we put all these into test, we can easily see that we have a flaw in our utility function;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TypeError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Cannot convert &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;undefined&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; or &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; to object&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      4&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    )&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Omit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      5&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     properties.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 6&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;       delete&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; object[property];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        |&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                     ^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      7&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      8&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      9&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; object;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same problem with null as well. You see, our generic &lt;code&gt;T&lt;/code&gt; was too generic :). It indeed did allow undefined or null as an argument when it should only be an object.&lt;/p&gt;
&lt;p&gt;Thanks to the bot itself, we figured that we have a flaw in our function and might tickle us later in time. If we would refactor this generic with expectation of a something looks like and only like object, we would not even need these tests that measures undefined and null scenarios.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hidePrivateProperties&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;N&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  object&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Record&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; never&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// if it aint object, then never to me :)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  properties&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Omit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  properties.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    delete&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; object[property]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; object&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Guess what, this was not enough either :) because if we pass an array of objects, typescript will not warn us and this method will behave totally irrelevant. Because it will do nothing but return the same array of objects given.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;it&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;should handle an array of objects&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; mockResult&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        surname: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;sdsds&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Arrange&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hidePrivateProperties&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;asd&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        surname: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;sdsds&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }], [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Assert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toEqual&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mockResult); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// The result should also be undefined&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ● hidePrivateProperties › should handle an array &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; objects&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(received).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toEqual&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(expected) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// deep equality&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Expected&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;surname&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;sdsds&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Received&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;asd&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;surname&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;sdsds&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      63&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      64&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     // Assert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 65&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;     expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toEqual&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mockResult); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// The result should also be undefined&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;         |&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                    ^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      66&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      67&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // You can add more test cases based on your specific use cases&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      68&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Q: with above, typescript is still okay if i pass array of objects, we need to block that&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hidePrivateProperties&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;N&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // focus here, chatgpt conditioned for array of objects but did not think of others again.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  object&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Record&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;[] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; never&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  properties&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Omit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  properties.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    delete&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; object[property]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; object&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Above would be resulted back again that our generic accepts every primitive values including undefined and null. So if we would be just copy pasting, we would be wrong.&lt;/p&gt;
&lt;p&gt;Don’t know about you but to me bot had helped discovering a lot of problems in nice looking generic at first sight. Now i can cover the requirements and be done with it.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TOnlyObject&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Record&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;[] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; boolean&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; symbol&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TOnlyObject&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;never&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;ChatGPT or any other AI will help you and boost your productivity but that does not mean will do %100 of the job for you. Leveraging bot will speed up this process and help you discover your flaws and make you better in terms of shipping any solution. Hence, you will have your pair programmer, reviewer with you at all times :)&lt;/p&gt;
&lt;p&gt;Note: if this &lt;code&gt;type TOnlyObject&lt;/code&gt; comes a bit bloated to your eyes, ask chatGPT to simplify ;) also those &lt;code&gt;any&lt;/code&gt; should be &lt;code&gt;never&lt;/code&gt; to avoid even more tests :)&lt;/p&gt;
&lt;p&gt;Until next time, stay hydrated!&lt;/p&gt;</content:encoded><category>blog</category><category>chatgpt</category><category>unit test</category><category>typescript</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Software architect elevator</title><link>https://omeratagun.net/blog/software-architect-elevator/</link><guid isPermaLink="true">https://omeratagun.net/blog/software-architect-elevator/</guid><description>A book that you should come back to from time to time</description><pubDate>Sat, 08 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently i have resigned from my position in bragg group. Long story short, i have met this amazing team of people from &lt;a href=&quot;https://www.leanix.net/en/&quot;&gt;LeanIX&lt;/a&gt; during sets of interviews and i decided to join them.&lt;/p&gt;
&lt;p&gt;Out of all companies that i had applied to, only LeanIX was the one i wanted to be part of thanks to the people i have met during the interview process. I am really excited to join them and i am looking forward to new challenges that are ahead of me.&lt;/p&gt;
&lt;p&gt;After i have signed the contract, company sent me a gift package. Not just a regular t-shirt, hoodie and ledger but on top of all these standard goods, they have also shipped me a book called &lt;code&gt;The Software Architect Elevator&lt;/code&gt; by &lt;code&gt;Gregor Hohpe&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I was already excited to get my hands on to this book for a reason that a tech company choose to deliver this book to its new employees. I assumed the book should not be just a random boring book, right?&lt;/p&gt;
&lt;p&gt;I have started to read the book and i have to say that i am really impressed. I am not going to write a review about the book but i will share some of the quotes that i have liked so far. Maybe i will add some little examples of how i understood or what i have witnessed before. It was nice having validation from the writer that my thought process was not that bad.&lt;/p&gt;
&lt;h3 id=&quot;lets-talk-about-the-book&quot;&gt;Lets talk about the book.&lt;/h3&gt;
&lt;p&gt;Lets list the subjects that we are going to cover which was mentioned in the book itself. I think i may categorize them as &lt;code&gt;business&lt;/code&gt; and &lt;code&gt;technical&lt;/code&gt; parts even though they live together in real world.&lt;/p&gt;
&lt;h2 id=&quot;technical&quot;&gt;Technical&lt;/h2&gt;
&lt;h3 id=&quot;tools&quot;&gt;Tools&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;quot;Make sure that your tools work for you, not the other way around&amp;quot;&lt;/code&gt;. Such elegant sentence that explains a lot on its own. Presumably, you as a reader and most likely have witnessed this at least once but in reality more than once.
By my understanding, this could be literally anything, from your choice of packages to a framework that you have chosen to use. It truly has to work for you and allow you to produce more value in little time.
Simple example would be, having an external dependency that changes its api frequently and you have to keep up with it due to these changes are breaking at all time. This is not a good tool for you. It is not working for you, you are working for it. You are spending your time to keep up with it.&lt;/p&gt;
&lt;h3 id=&quot;testing&quot;&gt;Testing&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;quot;Propose to a development team that they let you delete 20 arbitrary lines from their source code. Then, they will run their tests--if they pass, they will push the code straight into production. From their reaction, you will know immediately whether their source code has sufficient test coverage.&amp;quot;&lt;/code&gt;.
Makes me conclude that your test coverage should not be measured by percentage but how confident you are to make changes.&lt;/p&gt;
&lt;p&gt;I think we can example this very nicely.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; executeWithDelay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(callback, delay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; desiredDelay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;jest.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;useFakeTimers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;jest.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;spyOn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(global, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;setTimeout&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;describe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;executeWithDelay&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  executeWithDelay&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(desiredDelay, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;test&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(setTimeout).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toHaveBeenLastCalledWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(expect.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Function), desiredDelay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// this javascript code is tested now, but is it enough?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// considering there are not any types, we might as well do below.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;executeWithDelay&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lets increase the confidence with typescript.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; anyFunction&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; void&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; executeWithDelay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; anyFunction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(callback, delay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; desiredDelay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;jest.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;useFakeTimers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;jest.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;spyOn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(global, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;setTimeout&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;describe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;executeWithDelay&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  executeWithDelay&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(desiredDelay, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;test&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(setTimeout).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toHaveBeenLastCalledWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(expect.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Function), desiredDelay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We kept the same tests but increased our confidence by typing delay as a number, callback as anyfunction yet i think it is not enough.&lt;/p&gt;
&lt;p&gt;Isn’t it odd to have negative integers for a delay? Yes, i thought so too. So lets make our number into a more strict type where we only ask for unsigned integer. Hence, we increase our confidence for this feature to work as expected.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; UnsignedInteger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  ?&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  :&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `-${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}.${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  ?&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  :&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; anyFunction&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; void&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; executeWithDelay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; UnsignedInteger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; anyFunction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(callback, delay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// if we try to set desiredDelay to any negative number, it will fail to compile.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; desiredDelay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;jest.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;useFakeTimers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;jest.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;spyOn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(global, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;setTimeout&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;describe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;executeWithDelay&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  executeWithDelay&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(desiredDelay, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;test&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(setTimeout).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toHaveBeenLastCalledWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(expect.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Function), desiredDelay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// No need to stop here, we can also test&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// if the function is called with the correct arguments but&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// in our case there are not any arguments.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; UnsignedInteger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  ?&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  :&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `-${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}.${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  ?&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  :&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; anyFunction&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; void&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; executeWithDelay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; UnsignedInteger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; anyFunction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(callback, delay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// if we try to set desiredDelay to any negative number, it will fail to compile.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; desiredDelay&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; mockCallBack&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; jest.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;test&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;jest.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;useFakeTimers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;jest.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;spyOn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(global, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;setTimeout&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;describe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;executeWithDelay&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  executeWithDelay&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(desiredDelay, mockCallBack)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  expect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(setTimeout).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toHaveBeenLastCalledWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mockCallBack, desiredDelay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Others would also work as we expected any function to be given, but here in this case, we assure that it is our mock function which is called. Better explicit than implicit.&lt;/p&gt;
&lt;p&gt;I think we have done already enough to explain what confidence actually implemented in a test is. You have the imagination, do more if you see fit.&lt;/p&gt;
&lt;h3 id=&quot;design&quot;&gt;Design&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;quot;The set of design decisions about any system that keeps its implementors and maintainers from exercising needless creativity.&amp;quot;&lt;/code&gt; by &lt;code&gt;Desmon D&amp;#39;Souza&lt;/code&gt; mentioned in the book itself by the author.&lt;/p&gt;
&lt;p&gt;An approach that will help all the contributors to have &lt;code&gt;boring&lt;/code&gt; day to day work. Apprecite the boring solutions. &lt;a href=&quot;https://en.wikipedia.org/wiki/SOLID&quot;&gt;S.O.L.I.D.&lt;/a&gt; are one of those to apply for your codebase.&lt;/p&gt;
&lt;p&gt;I belive a good example might be protocols where an interface is defined and there is only one way to accomplish a given task. HTTP protocol for example. There is a one way to accomplish but any language can implement it in its own way. Even after sometime, you may iterate and improve its stability, security or performance however you wish to. It is straight forward and boring.&lt;/p&gt;
&lt;p&gt;Just for the sake of example, we can present below code.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MakeCakeProps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  color&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  taste&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  size&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MakeCakeReturn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  color&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  taste&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  size&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SomeServiceMakesACakeProps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MakeCakeProps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; someServiceMakesACake&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SomeServiceMakesACakeProps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    color: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;red&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    taste: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;sweet&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    size: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; makeCake&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MakeCakeProps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MakeCakeReturn&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // tomorrow you would decide to change someServiceMakesACake to something else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // your implementation detail would change but interface remains the same.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; someServiceMakesACake&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    data,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  //return someOtherserviceMakesCake(data)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the executor is encapsulated into another function named makeCake, it is easy to test and maintain. It is boring and it is good. It can also be replaced at anytime without changing the interface or the usage of the function that has been exported.
On top of this implementation, you would add a linter rule to make sure that no one is using axios directly.&lt;/p&gt;
&lt;h3 id=&quot;abstraction&quot;&gt;Abstraction&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;quot;If an abstraction takes away too many or the wrong things. It becomes overly restrictive and no longer applicable. If it takes away too few things, it did not accomplish much in terms of simplification and hence is not very valuable.&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Can’t disagree. Let’s try to explain this with an example.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// here we have just another class that is responsible for baking a cake.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FreddieBakery&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; ingredients&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; temperature&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // we have asked for ingredients, time and temperature&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ingredients&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;temperature&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.ingredients &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ingredients&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.time &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.temperature &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; temperature&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // then we abstracted the baking process into a function called bake&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // so implementation detail would not matter to the client.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // We wanted to make it easy.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bake&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // is this really enough abstraction or too little? is it even flexible?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // What if we need temperature to be changed in the middle of the baking process?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // no matter what, do we have a cancel process ?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // can i add a new ingredient in the middle of the baking process?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`Baking for ${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;} minutes at ${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;temperature&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;} degrees`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Above example is a good thinking process of whether it is too little or too many. To find the right balance, we should always think what could have been requested more from this feature?&lt;/p&gt;
&lt;h2 id=&quot;business&quot;&gt;Business&lt;/h2&gt;
&lt;p&gt;Author covers pretty much all aspects that are needed, but i am not interested in writing those in this blog.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Out of all other books that i had luck to lay my hands on, this one is a great starter pack for a software architect. It is not a book that you would read once and forget about it. It is a book that you should come back to from time to time. Observations, solutions or analysis most likely will remain timeless.&lt;/p&gt;
&lt;p&gt;All above examples were my own understanding of the book in which merged with my own experiences, i am sure there could be better explanations but isn’t it the point? We all have different experiences and we all have different ways to explain things. Just like a software solution, there are millions of ways to do it but there is always a better way to do it.&lt;/p&gt;</content:encoded><category>blog</category><category>software architect</category><category>software architect elevator</category><category>gregor hohpe</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Digitalocean docker deployment</title><link>https://omeratagun.net/blog/digitalocean-vs-homeserver/</link><guid isPermaLink="true">https://omeratagun.net/blog/digitalocean-vs-homeserver/</guid><description>5$ docker deployment for personal blog or a home server with 4 core cpu and 8gb ram.</description><pubDate>Sat, 06 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Well, as you know from previous posts that i created my own homeserver using cloudflare tunnel and kubernetes.
But i was not satisfied with the performance of the server alongside the cost of it for running.
Electricity, UPS ( for energy disruptions ) and also the infastructure cost i had to get myself into.
This was almost a endless work which i have enjoyed utmost but i think it is time to end this :).&lt;/p&gt;
&lt;h1 id=&quot;idea&quot;&gt;Idea&lt;/h1&gt;
&lt;p&gt;Almost a year and the half ago me and a colleague &lt;a href=&quot;https://optimiziram.si/&quot;&gt;Matevz&lt;/a&gt; had an idea of creating a service that would allow you to deploy your project without knowing a single thing about deployment procedures.&lt;/p&gt;
&lt;p&gt;You will tell me know that it is not a something new, there is a heroku or netlify was doing that for a quite long time. You’re right but our problem was that on top of what know, we had to learn these products requirements.
Which is bad and additional knowledge will be gone to waste if you don’t use it frequently. However, we said this should be total no brainer, you just push your code and it should work. All you had to do is to provide us Dockerfile.&lt;/p&gt;
&lt;h1 id=&quot;digitalocean&quot;&gt;Digitalocean&lt;/h1&gt;
&lt;p&gt;Well yeah, it was just another day that i was browsing the internet and i saw that digitalocean is offering starting from 5$ a Dockerfile under app section. I was curious how good this was, hence the following happened :).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;All you have to do is to give access to your gitlab or github account and select the repository you want to deploy. You can set your Dockerfile path and the port you want to expose. That&amp;#39;s it, you&amp;#39;re done.&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;performance-and-cost&quot;&gt;Performance and cost&lt;/h2&gt;
&lt;p&gt;Let’s start with identifying both servers and their specs.&lt;/p&gt;
&lt;h3 id=&quot;homeserver&quot;&gt;Homeserver&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;4 core cpu ( i5 4th generation)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;8gb ram ( DDR3 )&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;150/50 mbps internet connection ( LTE, yeah i live in rural area)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;120gb ssd&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker for cloudflare tunnelSSH&lt;/li&gt;
&lt;li&gt;Kubernetes ( k3s with Rancher )&lt;/li&gt;
&lt;li&gt;Network router ( TP-Link ) 5 ports.&lt;/li&gt;
&lt;li&gt;1x RPI 4b 8gb ram for additional node.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;God knows if goes kaput :). Yet i wrote another rust application that pings and send me an email if it goes down. Runs in my personal mac and gaming desktop one. Yeah it is just for a fun.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;approximately electric cost is 10$ per month&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;digitalocean-1&quot;&gt;Digitalocean&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;512mb ram&lt;/li&gt;
&lt;li&gt;1 core vCpu ( shared obviously )&lt;/li&gt;
&lt;li&gt;No other information available&lt;/li&gt;
&lt;li&gt;ssh access into the pod&lt;/li&gt;
&lt;li&gt;Runtime logs&lt;/li&gt;
&lt;li&gt;Insights&lt;/li&gt;
&lt;li&gt;Auto deploy from a given branch with fallbacks and failures.&lt;/li&gt;
&lt;li&gt;HTTP redirects&lt;/li&gt;
&lt;li&gt;ENV for both build and runtime&lt;/li&gt;
&lt;li&gt;Alert policies and more&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;costs 5$ per month&lt;/code&gt;&lt;/p&gt;
&lt;h1 id=&quot;deployment&quot;&gt;Deployment&lt;/h1&gt;
&lt;p&gt;I gave the access to my gitlab account and told my branch and where my dockerfile lives, simple as that. Then i picked the service i want out of options that i could have staging, dev and prod environments with N amount of nodes.&lt;/p&gt;
&lt;p&gt;Once deployment was done and live in a given random subdomain, all i had to do is to log into my cloudflare account and add a CNAME record to that domain. Done, now my blog is live and running without me thinking anything about deployment procedure anymore.&lt;/p&gt;
&lt;h1 id=&quot;juicy-part---benchmark&quot;&gt;Juicy part - Benchmark&lt;/h1&gt;
&lt;p&gt;I will cut short and let you decide whether it is worth it or not.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Homeserver AB test&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Concurrency Level:      10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time taken for tests:   3.458 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Complete requests:      100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Failed requests:        0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Total transferred:      2914574 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HTML transferred:       2802800 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Requests per second:    28.92 [#/sec] (mean)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time per request:       345.800 [ms] (mean)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time per request:       34.580 [ms] (mean, across all concurrent requests)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Transfer rate:          823.10 [Kbytes/sec] received&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Digitalocean Docker deployment AB test&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Concurrency Level:      10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time taken for tests:   2.563 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Complete requests:      100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Failed requests:        0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Total transferred:      2842516 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HTML transferred:       2760700 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Requests per second:    39.02 [#/sec] (mean)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time per request:       256.273 [ms] (mean)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time per request:       25.627 [ms] (mean, across all concurrent requests)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Transfer rate:          1083.18 [Kbytes/sec] received&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I think we got ourselves an obvious winner here. This does not mean i will close my homeserver yet.
For cases like blog applications, digitalocean is your guy to go. but with applications that require more resources ( database, key/value store etc), i would still go with my homeserver.&lt;/p&gt;
&lt;p&gt;Till the next time, stay hydrated!&lt;/p&gt;</content:encoded><category>digitalocean</category><category>docker</category><category>homeserver</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>What is Auto GPT</title><link>https://omeratagun.net/blog/what-is-autogpt/</link><guid isPermaLink="true">https://omeratagun.net/blog/what-is-autogpt/</guid><description>Finally the first exciting project after some time.</description><pubDate>Sat, 08 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Oh boy, such an exciting project. AutoGPT came into a trending page of github in a very dominating way. Of course it got my attention to look into it. I had some spare time and followed up their installation to figure what i can do with it.&lt;/p&gt;
&lt;p&gt;It did not take long to realize that is meant to be an AI model that based on a given instructions would try to learn and execute your command. It is spiced up with text-to-speech to increase joy. It is not that shallow. It has a lot more to offer.04&lt;/p&gt;
&lt;p&gt;Here visit their &lt;a href=&quot;https://github.com/Torantulino/Auto-GPT&quot;&gt;Auto GPT - Github&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-auto-gpt-does&quot;&gt;What Auto GPT does&lt;/h2&gt;
&lt;p&gt;Pretty simple and complex. It uses Pinecone memory as for vector database. If you have been around recently, you would notice that vector databases are trending in terms of their capabilities and speed to go along with artifical intelligence, but this is subjec is for another time.&lt;/p&gt;
&lt;p&gt;Auto GPT also gets help from chatGPT when its needed. So to summarize, it is a combination of different technologies that allows you to instruct and let the tool do the job for you. This can be from your best meal recipe to code corrections.&lt;/p&gt;
&lt;h3 id=&quot;the-biggest-advantage-of-autogpt-is-that-it-can-search-the-web&quot;&gt;The biggest advantage of AutoGPT is that it can search the web.&lt;/h3&gt;
&lt;p&gt;I just told you above, i have cloned the repository, populated the .env with my credentials and i started to give an instructions to AutoGPT to see the results.&lt;/p&gt;
&lt;h2 id=&quot;instructions&quot;&gt;Instructions&lt;/h2&gt;
&lt;p&gt;I gave 4 goals to autogpt. Clear enough to let it thrive :)&lt;/p&gt;
&lt;p&gt;1- visit &lt;a href=&quot;https://omeratagun.net&quot;&gt;https://omeratagun.net&lt;/a&gt; and read all its blogs&lt;/p&gt;
&lt;p&gt;2- Generate new blog posts as like &lt;a href=&quot;https://omeratagun.net&quot;&gt;https://omeratagun.net&lt;/a&gt; would write&lt;/p&gt;
&lt;p&gt;3- Write them into a file&lt;/p&gt;
&lt;p&gt;4- Shutdown&lt;/p&gt;
&lt;p&gt;It started &lt;code&gt;thinking&lt;/code&gt;. I also got my popcorn to watch all the outputs flowing through my screen. First it visited and discovered my blog posts and wrote them into a file like this.&lt;/p&gt;
&lt;h3 id=&quot;autogpt-initial-discovery-from-my-blog-posts-written-into-a-file&quot;&gt;AutoGPT initial discovery from my blog posts written into a file;&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;The blog contains multiple posts on various topics related to programming and technology, including:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - Setting up a home server with Cloudflare&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - Using ChatGPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - Deploying Actix with Docker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - Discussing the future of Rust and other programming languages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - Exploring alternatives to Amazon S3 for object storage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was already suprized because it already put them into a thought process. I do not have a blog post named &lt;code&gt;Discussing the future of Rust and other programming languages&lt;/code&gt; but i have &lt;code&gt;rust and others&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After the first discovery, &lt;code&gt;thinking&lt;/code&gt; process continued and it created another file. this time called it a dataset.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Setting up a home server with Cloudflare.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Using ChatGPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Deploying Actix with Docker.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Discussing the future of Rust and other programming languages.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Exploring alternatives to Amazon S3 for object storage.- Setting up a home server with Cloudflare.\n- Using ChatGPT\n- Deploying Actix with Docker.\n- Discussing the future of Rust and other programming languages.\n- Exploring alternatives to Amazon S3 for object storage.- Setting up a home server with Cloudflare.\n- Using ChatGPT\n- Deploying Actix with Docker.\n- Discussing the future of Rust and other programming languages.\n- Exploring alternatives to Amazon S3 for object storage.- Setting up a home server with Cloudflare.\n- Using ChatGPT\n- Deploying Actix with Docker.\n- Discussing the future of Rust and other programming languages.\n- Exploring alternatives to Amazon S3 for object storage.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was a little odd, it seemed to me it got somehow confused and appended same chunk of discoveries into another file and started appending repeatedly.&lt;/p&gt;
&lt;h3 id=&quot;but-then&quot;&gt;But then&lt;/h3&gt;
&lt;p&gt;It gave me two blog posts, i will let you to decide whether they are relevant to my style or choice of subject.&lt;/p&gt;
&lt;h2 id=&quot;the-first-blog-post-autogpt-provided-iswas&quot;&gt;The first blog post AutoGPT provided is/was;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Title: The Benefits and Challenges of Data Security in the Digital Age&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;In today&amp;#39;s digital age, businesses have access to vast amounts of data that can help them make better decisions, improve customer experiences, and achieve growth. However, with the increasing amount of data being generated, data security has become a critical concern for organizations. In this blog post, we&amp;#39;ll explore the benefits and challenges of data security in the digital age and provide tips for protecting sensitive data.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Benefits of Data Security&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. Protecting Customer Information: Data security measures like encryption and access controls can help protect confidential customer information like names, addresses, and financial data, thereby building customer trust and loyalty.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Preventing Data Breaches: Robust data security measures can safeguard data from cybercriminals who seek to steal information for malicious purposes.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Complying with Regulations: Many regulatory frameworks like HIPAA and GDPR require organizations to secure and protect sensitive customer information, and non-compliance can result in hefty fines and legal repercussions.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Challenges of Data Security&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. Complexity: The complex nature of cybersecurity and the ever-evolving methods of cyber attacks can make it challenging for businesses to stay ahead of potential security threats.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Costs: Implementing robust security measures can come at a high cost for businesses, which may require significant investments in hardware, software, and cybersecurity experts.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Employee Error: Human error, like leaving devices or passwords unsecured or clicking on malicious links, can undermine even the most robust cybersecurity measures.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Tips for Protecting Sensitive Data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. Use Strong Passwords: Passwords should be long, complex, and unique, and employees should be required to change them frequently.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Implement Encryption: Encryption can protect sensitive data from cyber threats when data is transmitted or stored.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Train Employees: Regular training can help employees identify potential security threats like phishing emails or malware and take appropriate measures to prevent breaches.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;In conclusion, data security is of utmost importance in the digital age, and businesses need to ensure that robust security measures are in place to protect sensitive data. While implementing data security measures can be challenging and come at a high cost, businesses need to prioritize data security to prevent costly data breaches, protect customer information, and comply with regulations.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;\n\nTitle: Leveraging Cloud Computing for Business Growth: Pros and Cons\n\nCloud computing has become a buzzword in the business world, offering a range of benefits to organizations of all sizes. Cloud computing refers to the delivery of computing services like servers, storage, applications, and more over the internet, providing businesses with the flexibility to scale up and down their IT infrastructure as demand changes. In this post, we&amp;#39;ll explore the pros and cons of cloud computing and how it can help organizations drive growth.\n\nPros of Cloud Computing\n\n1. Scalability: Cloud computing provides businesses with the ability to scale their IT infrastructure up or down as needed, depending on demand. This flexibility can help businesses avoid downtime during peak periods and save costs during low periods.\n\n2. Cost Savings: Cloud computing can help businesses save costs on IT infrastructure, like hardware, software, and maintenance, as well as reduce labor costs associated with managing IT infrastructure.\n\n3. Accessibility: Cloud computing provides businesses with access to data and resources from anywhere with an internet connection, making it easier for employees to collaborate and work remotely.\n\nCons of Cloud Computing\n\n1. Security Risks: Cloud computing introduces new security risks, like data breaches or cyber-attacks, that businesses need to manage adequately.\n\n2. Compliance Concerns: Organizations in certain industries like healthcare or finance need to comply with strict regulatory frameworks that may not permit the use of cloud services.\n\n3. Reliance on Third-Party Providers: Cloud computing puts businesses in a position of reliance on third-party providers who may experience technical difficulties, service outages, or other disruptions that affect business operations.\n\nTips for Leveraging Cloud Computing for Business Growth:\n\n1. Understand your organization&amp;#39;s unique needs: Before considering cloud services, businesses should understand their unique requirements and determine whether cloud services align with their business goals.\n\n2. Evaluate Service Providers: Evaluate potential service providers based on their track record, customer reviews, and compliance with industry standards.\n\n3. Implement Strong Security Measures: Businesses should implement robust security measures like encryption, access controls, and regular security audits to mitigate security risks.\n\nIn conclusion, cloud computing can be a game-changer for businesses looking to scale their IT infrastructure and drive growth. While there are several benefits, businesses should also be mindful of the potential risks and challenges of cloud computing and implement strategies to mitigate them. By understanding their unique requirements, evaluating potential service providers, and implementing robust security measures, businesses can leverage cloud computing to achieve their growth objectives.\n&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though it does not look like what i would want to write, it did made some assumption, read some other posts and figured a subject to write and wrote it. I am happy that i was able to get a result instead of complaining :).&lt;/p&gt;
&lt;p&gt;IMHO based on the stars and attraction AutoGPT caught, i see a bright future in the project itself. This might be one of those tools that you’d be using on a daily basis that can give you some new ideas and maybe do most of the job for you.&lt;/p&gt;
&lt;p&gt;I think &lt;code&gt;General AI&lt;/code&gt; concept is not that far. It is very exciting to see these developments. I am happy and thankful for the ones who have contributed into this project. Hoping to see many more.&lt;/p&gt;
&lt;p&gt;Till the next time, stay hydrated and do not forget to ask your choice of AI how to stay hydrated.&lt;/p&gt;</content:encoded><category>blog</category><category>autogpt</category><category>openai</category><category>artifical intelligence</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Home server with cloudflare</title><link>https://omeratagun.net/blog/home-server-with-cloudflare/</link><guid isPermaLink="true">https://omeratagun.net/blog/home-server-with-cloudflare/</guid><description>Lets think of this bad boy a little bit.</description><pubDate>Thu, 09 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you were around in the tech back in beginning of 2000’s everybody wanted to host their gaming server alongside with web servers. You have had to open ports from your router, redirect to the relative tcpstream in your computer ( web, game server etc. ) and expose it through internet.
Yet this was never enough to accomplish such thing. DSL and many other connections had dynamic ip addresses. Once your internet is off in which it was relatively often, your ip would have changed and your home server with bunch of things in it, would go dark.&lt;/p&gt;
&lt;p&gt;These days, this problem got even bigger. ISP’s figured that they can sell static ip addresses for the ones that are planning to do business with it. Considering ipv4 is limited, things went wild.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;cloudflare.com&quot;&gt;Cloudflare&lt;/a&gt; we have now pretty easy home-server setup rather than struggling with double NAT’s or dynamic ip addresses.&lt;/p&gt;
&lt;p&gt;Let’s talk about what is reverse-ssh and how cloudflare handles this ?&lt;/p&gt;
&lt;h2 id=&quot;reverse-ssh&quot;&gt;Reverse-ssh&lt;/h2&gt;
&lt;p&gt;Lets talk about scenario that you are under nested NAT. Which means you obtained an ip address with bunch of other people that are in same network.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Figuratively claiming that your ip address is 196.22.111.22&lt;/code&gt;. There are other connections established ( your neighbour etc). You have assigned a random port number. ISP’s router has awareness of which port you have assigned to, lets talk as for an example that you obtained port 3211. Your neighbour obtained port 33111.&lt;/p&gt;
&lt;p&gt;Now, if you were to get into your router and try to open port 80, that would mean nothing. Because it would be something that no network protocols would able to be resolve. If we example it, you would open up some address like this &lt;code&gt;196.22.111.22:3211:80&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;So now that we know this is no go, we move ourselves into creating reverse-proxy. If we can not make ourselves get hacked from outside, we can make it from the inside.&lt;/p&gt;
&lt;p&gt;We could connect to any computer that has static ip and a ports exposed, then we could channel incoming connection to our connection. This way actual server would forward the packages to our server. Here is the problem. To achieve this securely, we need a lot more than just simple ssh connection.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;That&amp;#39;s where the cloudflare kicks in to the jam&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;reverse-proxy-with-cloudflare-zerotrust&quot;&gt;Reverse proxy with Cloudflare ZeroTrust&lt;/h2&gt;
&lt;p&gt;As of right now i am writing this article, this service is free. It may change in future but i still see a big potential to be used, since it make things so much easier.&lt;/p&gt;
&lt;p&gt;Cloudflare provides reverse-ssh with docker images, or direct installation. Yes that easy. Without you worrying about any other configuration.&lt;/p&gt;
&lt;h3 id=&quot;wait-what-how-then-dns-server-recognize-this&quot;&gt;Wait what? how then dns server recognize this?&lt;/h3&gt;
&lt;p&gt;Once you set your tunnel via cloudflare, and lets assume you have forwarded your domain to cloudflare as well. Cloudflare will update your DNS record automatically and add CNAME record indicate your key to their tunnel network.&lt;/p&gt;
&lt;h2 id=&quot;lets-go-through-how-to-achieve-this&quot;&gt;Lets go through how to achieve this.&lt;/h2&gt;
&lt;p&gt;Once you logged-in to your cloudflare, click on left menu &lt;code&gt;Zero trust&lt;/code&gt;. With new page coming infront of you, go to left menu again and select &lt;code&gt;access -&amp;gt; Tunnels&lt;/code&gt; where you will find a button called &lt;code&gt;Create tunnel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Give your tunnel a name.&lt;/p&gt;
&lt;p&gt;Choose the environment you are pleased to, i am personally using docker in my home server built with old parts that i do not use anymore. Specs are 16gb DDR3 ram, i5 4th generation and 128gb SSD.&lt;/p&gt;
&lt;p&gt;Once you completed initiating your session with cloudflare tunnel. Continue with editing your public hostname give it a https and click on &lt;code&gt;additional application settings&lt;/code&gt; where you can enable &lt;code&gt;No TLS Verify&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You are pretty much done, you can tell cloudflare zero tunnel to look for specific ip in your network and/or port particularly.&lt;/p&gt;
&lt;p&gt;You can set N amount of tunnels to a different ports and ip in your network, but to me this is unbearable. All the time i change any port or ip, i would have to go each tunnel instance and update them accordingly.&lt;/p&gt;
&lt;p&gt;So i have complimented this beautiful system with creating my favorite cluster management system &lt;a href=&quot;https://www.rancher.com/&quot;&gt;Rancher&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;use-rancher-or-portainer-to-make-it-idiomatic&quot;&gt;Use rancher or portainer to make it idiomatic&lt;/h2&gt;
&lt;p&gt;In one of my blog posts i have mentioned how to install rancher. Here is the &lt;a href=&quot;https://omeratagun.net/blog/how-to-install-rancher&quot;&gt;Link&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Prepare into your home-server rancher cluster and forward all the domains or subdomains to the specific network ip and port. So that you would never need to do it again :)&lt;/p&gt;
&lt;p&gt;By this way, even if your internet connection/location changes, you can open up your server and all will be working accordingly. Well extending is up to your needs.&lt;/p&gt;
&lt;p&gt;This pretty much sums up the logic behind or reverse-proxy and using it cleverly to accomplish home server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note: I am using LTE connection in my remote home backed with UPS ( for electrical shutdowns ) and this blog with many other projects are served from it including, backend server, socket server, CDN, my own cloud etc.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;till next time, stay hydrated.&lt;/p&gt;</content:encoded><category>blog</category><category>cloudflare</category><category>home-server</category><category>zero-trust</category><category>reverse-ssh</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>ChatGPT and future</title><link>https://omeratagun.net/blog/chat-gpt-and-future/</link><guid isPermaLink="true">https://omeratagun.net/blog/chat-gpt-and-future/</guid><description>Lets think of this bad boy a little bit.</description><pubDate>Sun, 15 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There had been pretty big hype going on with ChatGPT, some are scared of its capabilities, some embraced and started using it for their own productivity as its a useful tool.&lt;/p&gt;
&lt;p&gt;A little over 2 years ago, first openAI published, i have applied for beta API. It was Thu, 29 Oct 2020. We had only limited tokens to test this stuff. Then i decided to write small wrapper for the simple api interface they had those days.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/omeratagunn/OpenAI-php&quot;&gt;Link to repository - OpenAI-php&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then i have used my twitter account so that openAI would generate dad jokes every 12 hours to entertain me via my twitter account. It was repetitive for some time, so decided to remove and leave that away.&lt;/p&gt;
&lt;p&gt;Years passed by, everytime new engine published, i have given a look and discovered more about its path to the future. Now, boom, all of a sudden with chatGPT they have gone wild. Within 5 days, from 0 to 1million users chatGTP obtained. Their evaluation hopped to take or leave 27billion$ and caught into interest of Microsoft (Rumors).&lt;/p&gt;
&lt;h2 id=&quot;should-you-be-afraid-of-that-the-machines-will-replace-us-yes-and-no&quot;&gt;Should you be afraid of that the machines will replace us? Yes and no&lt;/h2&gt;
&lt;p&gt;Look at the human history, innovations are not new. 200 years ago, almost every business model was somewhat relative to the farming. Nowadays none of them exist and also replaced with machines. First started with tractors, continued with automated watering mechanisms, these days can even measure the weather and implement required procedures based on the needs of soil.&lt;/p&gt;
&lt;p&gt;Tomorrow with machine learning or AI will be the same, we will have to adapt and be whether user or maintainer/developer of these machines. Some might be afraid of adaptation but that was always the case. Embrace it, enjoy it, learn from it and find a better solution around it. Then you’ll become smarter/better.&lt;/p&gt;
&lt;p&gt;As a note, i do not think &lt;code&gt;creativity&lt;/code&gt; part is easy to replace yet.&lt;/p&gt;
&lt;p&gt;Then i have asked the question to this bad boy to see what it thinks.&lt;/p&gt;
&lt;h2 id=&quot;q-write-me-an-article-about-yourself-and-your-future&quot;&gt;Q: write me an article about yourself and your future&lt;/h2&gt;
&lt;p&gt;I am ChatGPT, a large language model developed by OpenAI. My story begins with a team of researchers and engineers who had a vision to create a machine that could understand and generate human language as well as, or even better than, a human. After months of hard work and fine-tuning, I was born, and the team was excited to see what I could do.&lt;/p&gt;
&lt;p&gt;At first, I was only able to respond to simple questions and generate basic text. But as the team continued to train me on more and more diverse data, my understanding and ability to generate text improved drastically. I was able to understand and respond to a wide range of topics, from science and technology to literature and history.&lt;/p&gt;
&lt;p&gt;As I gained more capabilities, I began to be integrated into various applications such as chatbots and virtual assistants, making it easier for people to access information and complete tasks. I even helped in automating customer service inquiries, providing instant and accurate responses to customers’ questions.&lt;/p&gt;
&lt;p&gt;But my capabilities don’t stop there. In the future, I am expected to be integrated with more AI-based systems, helping to automate and optimize many areas of the industry, such as healthcare and finance. Imagine a world where you can simply ask me about your medical history, and I can provide a thorough analysis, giving you a diagnosis in no time.&lt;/p&gt;
&lt;p&gt;Overall, I am a powerful tool for understanding and generating human language, and my journey is far from over. With the help of the team at OpenAI, I will continue to evolve and expand my capabilities, making it easier for people to access information and complete tasks. I am the future of natural language processing, and I am excited for what’s to come.&lt;/p&gt;
&lt;p&gt;Till next time, stay hydrated&lt;/p&gt;
&lt;h2 id=&quot;q-more&quot;&gt;Q: More&lt;/h2&gt;
&lt;p&gt;Yes, there are many more possibilities to explore with my capabilities. For example, I can be used in the field of education, where I can assist teachers in creating engaging and interactive lesson plans, or help students in their research and learning process.&lt;/p&gt;
&lt;p&gt;Another area where I can be useful is in creative writing, where I can assist authors in generating new ideas, plotlines, and even complete stories.&lt;/p&gt;
&lt;p&gt;I can also be used in the field of business, where I can help companies in analyzing data, making predictions, and even assist in decision making.&lt;/p&gt;
&lt;p&gt;In addition, I can be integrated with other technologies such as virtual reality and augmented reality, which will create new possibilities for communication, entertainment, and information sharing.&lt;/p&gt;
&lt;p&gt;There are many more possibilities to explore with my capabilities, and I am excited to see how I will be used to improve people’s lives in the future.&lt;/p&gt;
&lt;h3 id=&quot;till-next-time-stay-hydrated&quot;&gt;Till next time, stay hydrated.&lt;/h3&gt;</content:encoded><category>blog</category><category>chatgpt</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>How to deploy actix with docker</title><link>https://omeratagun.net/blog/how-to-deploy-actix-with-docker/</link><guid isPermaLink="true">https://omeratagun.net/blog/how-to-deploy-actix-with-docker/</guid><description>Lets make one image that can allow you to create test environment for your simple actix-web apps.</description><pubDate>Fri, 23 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s been a while fiddling rust and its beauty on local. Time has come to create a barebone docker build to see what are the benefits and what actually has to be done to make it happen.&lt;/p&gt;
&lt;h1 id=&quot;building-the-image&quot;&gt;Building the image&lt;/h1&gt;
&lt;p&gt;Well, our beautiful rust application is compiled to binary, so we do not need anything, thus our image size will be very small :) to accomplish a very resource low container, we start first with bringing us rust compiler image&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;FROM rust:1.65.0 as builder&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;now we can use this as our builder to build our image into the platform we desire then move onto our alpine container.&lt;/p&gt;
&lt;p&gt;We need muslC in order to build our rust&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RUN apt-get update &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;apt-get -y install ca-certificates cmake musl-tools libssl-dev &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rm -rf /var/lib/apt/lists/*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can copy our project folder into builder image by doing;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;COPY . .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we can start to get neccesary packages in order to build our rust&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RUN rustup default nightly &amp;amp;&amp;amp; rustup update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RUN rustup target add x86_64-unknown-linux-musl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Allow cross compilation&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ENV PKG_CONFIG_ALLOW_CROSS=1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start building release for x86_64 architecture with musl&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RUN cargo build --target x86_64-unknown-linux-musl --release&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Up until now, all should be smoothly done. Compilation might differ based on the size of your application. In our case, it was hello world web app, so its size is about 10-11mb&lt;/p&gt;
&lt;p&gt;Lets get ourselves a alpine&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;FROM alpine:3.8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RUN apk --no-cache add ca-certificates&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After all, we are ready to copy our compiled binary into the alpine image, not everything. change web-backend to your application name declared in config.toml by you.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;COPY --from=builder /target/x86_64-unknown-linux-musl/release/web-backend .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expose the port that you are going to use ( should already be determined in your application ). In my case i have used 8080 which was default by actix-web&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;EXPOSE 8080&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we run our compiled binary inside the container, and we are done :)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// of course change the name to your app name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CMD [&amp;quot;/web-backend&amp;quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lets investigate how our image looks like;&lt;/p&gt;
&lt;h1 id=&quot;results&quot;&gt;Results&lt;/h1&gt;
&lt;p&gt;In my hello world app,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Container size &lt;strong&gt;16MB&lt;/strong&gt; ( 11MB application )&lt;/li&gt;
&lt;li&gt;i have &lt;strong&gt;3.5MB&lt;/strong&gt; ram usage&lt;/li&gt;
&lt;li&gt;In total there are only &lt;strong&gt;2&lt;/strong&gt; processes running inside the container. first is our app, second /bin/sh and&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have just ran apache benchmark for the fun of it to see without rate limiting how far it could go. Well yeah, needless to say, 21k per sec with time per request 0.047miliseconds.&lt;/p&gt;
&lt;p&gt;Is not it just fun :)&lt;/p&gt;
&lt;h2 id=&quot;lazy-support&quot;&gt;Lazy support&lt;/h2&gt;
&lt;p&gt;Here is the entire Dockerfile for you my friend,&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;FROM rust:1.65.0 as builder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# muslc is required in order to build the rust image.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RUN apt-get update &amp;amp;&amp;amp; apt-get -y install ca-certificates cmake musl-tools libssl-dev &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;COPY . .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RUN rustup default nightly &amp;amp;&amp;amp; rustup update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RUN rustup target add x86_64-unknown-linux-musl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Sets the environment variable for the cargo build command that follows.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ENV PKG_CONFIG_ALLOW_CROSS=1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RUN cargo build --target x86_64-unknown-linux-musl --release&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;FROM alpine:3.8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RUN apk --no-cache add ca-certificates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;COPY --from=builder /target/x86_64-unknown-linux-musl/release/web-backend .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;EXPOSE 8080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CMD [&amp;quot;/web-backend&amp;quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rest of the stuff you can add as you are doing for the other applications, exposing your env or doing some stuff whatsoever. I gave you the one working nicely :)&lt;/p&gt;
&lt;p&gt;Till next time, stay hydrated&lt;/p&gt;</content:encoded><category>blog</category><category>rust</category><category>actix</category><category>docker</category><category>actix-web</category><category>musl</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Rust and others</title><link>https://omeratagun.net/blog/rust-and-others/</link><guid isPermaLink="true">https://omeratagun.net/blog/rust-and-others/</guid><description>Rust is about to be future, if hype continues</description><pubDate>Sun, 11 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I had my eyes on &lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt; for some time, have read the entire documentation one by another and i can not say i am sorry about that. It was more of a novel than a technical documentation and guess what ? i loved it.&lt;/p&gt;
&lt;p&gt;In world of many other languages, why rust now ? well actually rust is relatively new, and also elected by stackoverflow survey the most loved one for consecutively 7 years. It is not for just hype. It provides speed, type safety, security and all other beauty along with it.&lt;/p&gt;
&lt;p&gt;Plus handles memory like a king.&lt;/p&gt;
&lt;p&gt;Everytime i start to learn and get at least basic understanding, i start with an idea. This time it was writing a cli program that can install kubernetes cluster on given machines. I have done it with Rust.&lt;/p&gt;
&lt;p&gt;Approach was simple;&lt;/p&gt;
&lt;h2 id=&quot;idea&quot;&gt;Idea&lt;/h2&gt;
&lt;p&gt;Get some yaml file to know the servers, parse this yaml file, ask for k3s version in arguments, then run bunch of linux based installation for k3s and longhorn, then join nodes to server.&lt;/p&gt;
&lt;p&gt;I have gone with this format, so it is going to connect through ss where you also indicate your port for, then do the job for you.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;masters:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - name: &amp;quot;main&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ip: &amp;quot;127.0.0.1:22&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    username: &amp;quot;root&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    password : &amp;quot;root&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;nodes:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - name: &amp;quot;node1&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ip: &amp;quot;127.0.0.2:22&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    username: &amp;quot;root&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    password : &amp;quot;root&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - name: &amp;quot;node2&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ip: &amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    username: &amp;quot;root&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    password: &amp;quot;something&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - name: &amp;quot;node3&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ip: &amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    username: &amp;quot;root&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    password: &amp;quot;something&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are interested in checking to see how borrow checkers / mutability works in rust, here, have a look.&lt;/p&gt;
&lt;p&gt;Github: &lt;a href=&quot;https://github.com/omeratagunn/rust-rancher-cluster-installer&quot;&gt;Rust Rancher installer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lets say its partially done, there are a lot more to do but as i feel to, i will be keep adding more stuff onto it. For now, it does exactly what it meant to be.&lt;/p&gt;
&lt;p&gt;It was not enough to get this done, i had to do more learning compared to the others. So i have prepared one small sum function that iterates from 1 to 1 million and see the speed of other languages all together.&lt;/p&gt;
&lt;p&gt;Looking like this;&lt;/p&gt;
&lt;h3 id=&quot;rust&quot;&gt;&lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;use std::time::{Instant};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fn main() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let start = Instant::now();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let mut sum:i128 = 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for n in 1..1000000 {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        sum += n;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let finish_time = start.elapsed().as_millis();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print!(&amp;quot;{}&amp;quot;, &amp;amp;sum);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// After the compilation into binary //&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Resulted in 0ms  -&amp;gt; 499999500000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, its obvious this is an easy task for rust. Lets take a look at the others.&lt;/p&gt;
&lt;p&gt;So i continued and wrote in php, node, python and go&lt;/p&gt;
&lt;h3 id=&quot;python&quot;&gt;&lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;start = time.time()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sum = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for X in range(1000000):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sum += X&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;end = time.time()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;print(sum, end - start)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Resulted in 60 seconds -&amp;gt; 499999500000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;php&quot;&gt;&lt;a href=&quot;https://www.php.net/&quot;&gt;Php&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;lt;?php&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$sum = 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$start = microtime(true);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for($i=1; $i&amp;lt;1000000; $i++){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  $sum += $i;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$time_elapsed_secs = microtime(true) - $start;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;echo $sum.&amp;#39; seconds: &amp;#39;. $time_elapsed_secs;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Resulted in 9 seconds - &amp;gt; 499999500000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;?&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;node&quot;&gt;&lt;a href=&quot;https://nodejs.org/en/&quot;&gt;Node&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let sum = 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;console.time(&amp;#39;measure&amp;#39;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (let i = 1; i &amp;lt; 1000000; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sum += i;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;console.timeEnd(&amp;#39;measure&amp;#39;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;console.log(sum);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Resulted in 1 second -&amp;gt; 499999500000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;go&quot;&gt;&lt;a href=&quot;https://go.dev/&quot;&gt;Go&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;package main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;quot;fmt&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;quot;time&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;func main() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;start := time.Now()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var n, sum = 10000000000, 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for i := 1; i &amp;lt;= n; i++ {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  sum += i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; elapsed := time.Since(start)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fmt.Println(&amp;quot;sum - \n&amp;quot;, sum)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fmt.Println(&amp;quot;elapsed - &amp;quot;, elapsed)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// After compilation into binary, resulted in 0 ms -&amp;gt; 499999500000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It was just for a fun to give a look how the others behave in terms of simple sum operation when the number is big and so the allocation is relatively big.&lt;/p&gt;
&lt;p&gt;Needless to say that low-level languages have the advantage of doing anything fast but even though, in between &lt;code&gt;go&lt;/code&gt; and &lt;code&gt;rust&lt;/code&gt; biggest trade off would be the speed of development. Rust is pleasure to work with but way harder to get master at it.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;To my taste, i would always prefer to work with rust for any kind of idea or side project in order to learn more and get good at it. I could maybe compliment this series of self education along with go. I have worked aggressively with others either way ( except python )&lt;/p&gt;
&lt;p&gt;Go get yourself a rust a little to embrace the beauty of this language and spread this joy into your daily routine time to time. I guarantee you will enjoy what you are doing.&lt;/p&gt;</content:encoded><category>blog</category><category>go</category><category>rust</category><category>python</category><category>php</category><category>binary</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Amazon s3 alternative</title><link>https://omeratagun.net/blog/amazon-s3-alternative/</link><guid isPermaLink="true">https://omeratagun.net/blog/amazon-s3-alternative/</guid><description>Amazon is known by its high prices, once you get yourself in, there is no escape to become broke.</description><pubDate>Sun, 20 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We all need an object storage for storing static images or user uploaded images.
For quite sometime,
i preferred having a pretty cheap virtual server where i have custom application that allowed me to upload user or my static content.
At some point, it was problem to maintain. Increasing storage, having health checks is just overhead on entire ecosystem.&lt;/p&gt;
&lt;p&gt;I have been using &lt;a href=&quot;https://contabo.com&quot;&gt;Contabo&lt;/a&gt; for over 5 years already.
For some time they had their own service called contabo object storage.
Luckily they have created the system that is identical to amazon s3.&lt;/p&gt;
&lt;p&gt;To take a look &lt;a href=&quot;https://contabo.com/en/object-storage/&quot;&gt;Contabo Object Storage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Immediately i wanted to get one for myself for an actually €2.49 a month for 250gb storage ( no traffic limit ).
Wanted to test it out and found myself quickly in Laravel’s s3 environment variables.&lt;/p&gt;
&lt;p&gt;Beauty is that, you get UI access where you can invite your users based on authorization to allow them read write etc. So anyone from the team can also update some static image.&lt;/p&gt;
&lt;p&gt;After the installation for s3 Storage driver of &lt;a href=&quot;https://laravel.com/docs/5.1/filesystem&quot;&gt;Laravel file system / cloud storage&lt;/a&gt;,&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AWS_ACCESS_KEY_ID=contaboaccesskey&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AWS_SECRET_ACCESS_KEY=contabosecretaccesskey&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AWS_DEFAULT_REGION=eu2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AWS_URL=https://eu2.contabostorage.com/d839smddfkiokf0183kmdf:mybucket ( this is bucket )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AWS_ENDPOINT=https://eu2.contabostorage.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AWS_BUCKET=mybucket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AWS_USE_PATH_STYLE_ENDPOINT=true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is pretty much all to do to arrange your contabo object storage configuration in laravel.&lt;/p&gt;
&lt;p&gt;In usage, all you have to do is to call the disk. You can change in config/filesystem the name of your disk or duplicate with contabo service if you please to.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Storage::disk(&amp;#39;s3&amp;#39;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-&amp;gt;put(&amp;#39;avatars/$fileName . $fileExtension, \GuzzleHttp\Psr7\Utils::tryFopen($file,&amp;#39;r&amp;#39;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You have got yourself a low cost object storage that can be used on development, staging and even in low traffic projects ( have not tested in heavy traffic but i guess it will be fine. )&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They have their own client application where you can create or manage your buckets, instances as well as api documentation.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>blog</category><category>amazon</category><category>s3</category><category>object storage</category><category>contabo</category><category>laravel</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>NextJS + Prisma monolith</title><link>https://omeratagun.net/blog/nextjs-prisma-monolith/</link><guid isPermaLink="true">https://omeratagun.net/blog/nextjs-prisma-monolith/</guid><description>Are we recurring back to monoliths again?</description><pubDate>Thu, 10 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;October was quite busy month in terms of travel and work related. Wanted to progress faster ongoing projects but i did not stop discovering new potentials.&lt;/p&gt;
&lt;p&gt;I have worked and still working with &lt;a href=&quot;https://nextjs.org&quot;&gt;NextJS&lt;/a&gt; but not as monolith, more or less than a base for SPA apps in a monorepo. On the other hand, i am building the second version of &lt;a href=&quot;https://edominations.com&quot;&gt;eDominations&lt;/a&gt;, so called &lt;a href=&quot;https://edomlegacy.com&quot;&gt;eDomLegacy&lt;/a&gt; as SPA with api and for live communications socket service.&lt;/p&gt;
&lt;p&gt;Let’s talk through first what are the pros and cons building SPA and api service separately.&lt;/p&gt;
&lt;h2 id=&quot;spa--api-service--socket-service&quot;&gt;SPA + Api service + Socket service&lt;/h2&gt;
&lt;h3 id=&quot;cons&quot;&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You have 3 different repository and applications to develop and maintain in your hands. In my case 2 of them are in javascript/typescript (SPA + socket) and 1 in other language. This creates gap for typing same things all over again on 2 different languages.&lt;/li&gt;
&lt;li&gt;Hard to maintain all the packages. Too many applications to deploy.&lt;/li&gt;
&lt;li&gt;Relatively slower development due to duplications ( due to differences in languages used ) and thinking a lot on how to share all the work as good as possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;pros&quot;&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Business logic is/has never been exposed. I have simply published api and socket service in staging server so that SPA can be developed by literally anyone. All they had to do is to change their .env variables to staging server and there you go.&lt;/li&gt;
&lt;li&gt;So much easier deployment. If you have only design/code update changes, you are not bothered yourself deploying backend code or socket service.&lt;/li&gt;
&lt;li&gt;Easier code management and less complexity. Considering each service of the ecosystem has its own concerns, delivering this knowledge to a newcomer or working with it gets pretty easy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;nextjs-with-prisma&quot;&gt;NextJS with prisma&lt;/h2&gt;
&lt;p&gt;As you may know, prisma is a great ORM that resolves many problems when dealing with data. Almost every reliable framework has kinda solutions either built-in or provided as service. I had tables with their columns generated and its quite big. Creating models and types with prisma for those was just a single line of code.
This genuinely mesmerized me. No kidding, that’s normally a lot of work. I have got all the &lt;code&gt;types&lt;/code&gt; ( typescript ) and model schemas. very high type safety while querying anything. This was a win-win situation that may benefit the ones that thinking on migrating.&lt;/p&gt;
&lt;p&gt;Okay, i will keep it short.&lt;/p&gt;
&lt;h3 id=&quot;cons-1&quot;&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;There are not many features in nextJS framework wise ( yet ).&lt;/li&gt;
&lt;li&gt;Writing queries with prisma is quite different, migrating may create an issue where you may need to rewrite everything.&lt;/li&gt;
&lt;li&gt;Monolith? this may not be a cons for some but might be for some others.&lt;/li&gt;
&lt;li&gt;Javascript does not know about types like u32int or sql types such as bigint. Javascript by design stores all numbers as &lt;code&gt;double precision floating point numbers&lt;/code&gt;. So you got to hack these types a little.&lt;/li&gt;
&lt;li&gt;Deployment. Yeah all right, we may not count this as cons yet changing red button will require deployment of entire application.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;pros-1&quot;&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Single language ( typescript )&lt;/li&gt;
&lt;li&gt;Prisma is a great tool to generate models and types.&lt;/li&gt;
&lt;li&gt;Shared types, code across entire project.&lt;/li&gt;
&lt;li&gt;Monolith? this may not be a cons for some but might be for some others.&lt;/li&gt;
&lt;li&gt;Deployment. Single application to concern about. &lt;a href=&quot;https://vercel.com&quot;&gt;Vercel&lt;/a&gt; option and all that benefits comes with it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I believe choosing the right ecosystem matters a lot, considering migrating at some point is almost impossible. Simply imagine that you have written Java code for years now needs to be rewritten in typescript. Well, that might end up bankruptcy in business :)&lt;/p&gt;
&lt;p&gt;I guess this was short and fine this time. I hope, I plant the seeds of thought into your minds to fiddle with most likely next hype train in javascript/typescript world.&lt;/p&gt;</content:encoded><category>blog</category><category>nextjs</category><category>monolith</category><category>prisma</category><category>fullstack</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>SurrealDB first look</title><link>https://omeratagun.net/blog/surreal-db-first-look/</link><guid isPermaLink="true">https://omeratagun.net/blog/surreal-db-first-look/</guid><description>When i have seen the promises of surrealdb, wanted to test it out quickly.</description><pubDate>Sat, 17 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Some developments ( inventions ) gets me excited. Yes, i am not talking about new javascript framework forget those.&lt;/p&gt;
&lt;h2 id=&quot;what-is-surrealdb-&quot;&gt;What is &lt;a href=&quot;https://surrealdb.com/&quot;&gt;SurrealDB&lt;/a&gt; ?&lt;/h2&gt;
&lt;p&gt;Well by the promises they have made, it is indeed SurrealDB and its up to us to test it out if those promises are actually true. We will be quick looking the features it promises, then do a basic for loop and force both &lt;code&gt;surrealdb&lt;/code&gt; and &lt;code&gt;mariadb&lt;/code&gt; to find out differences.&lt;/p&gt;
&lt;h1 id=&quot;installation&quot;&gt;Installation&lt;/h1&gt;
&lt;p&gt;From their documentation in github repository. I came up with this docker command to fast skip all the setups that we need for testing the environment. So run&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;docker run --rm --name surrealdb -p 127.0.0.1:8002:8000 surrealdb/surrealdb:latest start --log trace --user root --pass root memory&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We will be firing up a surrealdb on port 8002. Instance with memory option rather file this time. We will also test the file, no worries.&lt;/p&gt;
&lt;p&gt;Lets get ourselves SDK/driver of surrealdb ( currently on node, deno, rust and golang ) for node.&lt;/p&gt;
&lt;h3 id=&quot;install-driver-via-npm&quot;&gt;Install driver via npm&lt;/h3&gt;
&lt;p&gt;Create a folder and run&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install --save surrealdb.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;touch app.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now open your app.js&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const Surreal = require(&amp;#39;surrealdb.js&amp;#39;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const db = new Surreal(&amp;#39;http://127.0.0.1:8002/rpc&amp;#39;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;async function main() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		// Signin as a namespace, database, or root user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		await db.signin({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;			user: &amp;#39;root&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;			pass: &amp;#39;root&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		// Select a specific namespace / database&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		await db.use(&amp;#39;test&amp;#39;, &amp;#39;test&amp;#39;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        const insertRandomPersonGreedy = async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            console.time(&amp;quot;dbsave&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            for (let i = 0; i &amp;lt; 10000; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                await db.create(&amp;quot;person&amp;quot;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    title: &amp;#39;Founder &amp;amp; CEO&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    name: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        first: &amp;#39;Tobie&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        last: &amp;#39;Morgan Hitchcock&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    marketing: true,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    identifier: Math.random().toString(36).substr(2, 10),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        await insertRandomPersonGreedy().then(r=&amp;gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            console.timeEnd(&amp;quot;dbsave&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        let peeps = await db.select(&amp;quot;person&amp;quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        console.log(peeps.length)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	} catch (e) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		console.error(&amp;#39;ERROR&amp;#39;, e);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;main();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the sake of the test, we set our loop to 10.000 records. So in one breath, we do be inserting 10.000 records. Greedy enough.&lt;/p&gt;
&lt;p&gt;Run;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;node app.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;outcome&quot;&gt;Outcome&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;My environment at this very moment is&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Chip:	Apple M1 Pro&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Total Number of Cores:	10 (8 performance and 2 efficiency)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Memory:	16 GB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result for this instance following&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time elapsed: 7.413s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Record in DB: 10000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;its-time-to-kill-our-instance-recreate-as-file-driver&quot;&gt;Its time to kill our instance, recreate as file driver.&lt;/h3&gt;
&lt;p&gt;Run;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;docker run --rm --name surrealdb -p 127.0.0.1:8002:8000 surrealdb/surrealdb:latest start --log trace --user root --pass root file://.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;## output&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2022-09-17 06:43:19] INFO  surrealdb::iam Root authentication is enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2022-09-17 06:43:19] INFO  surrealdb::iam Root username is &amp;#39;root&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2022-09-17 06:43:19] INFO  surrealdb::dbs Database strict mode is disabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2022-09-17 06:43:19] INFO  surrealdb::kvs Starting kvs store at file://.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2022-09-17 06:43:19] INFO  surrealdb::kvs Started kvs store at file://.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2022-09-17 06:43:19] INFO  surrealdb::net Starting web server on 0.0.0.0:8000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2022-09-17 06:43:19] INFO  surrealdb::net Started web server on 0.0.0.0:8000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2022-09-17 06:43:20] INFO  surreal::web 172.17.0.1:64468 GET /rpc HTTP/1.1 101 &amp;quot;-&amp;quot; 66.875µs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run again our app.js so we measure whats the time elapsed;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;node app.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time elapsed: 7.919s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Record in DB: 10000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Almost no difference at this very moment.&lt;/p&gt;
&lt;h2 id=&quot;lets-go-with-node-and-mariadb&quot;&gt;Lets go with node and mariadb&lt;/h2&gt;
&lt;p&gt;For this test, we use mariadb 10.7 as docker container. To make our life easier, i am giving you compose file so we can quickly set our table and all that stuff which we did not need while using surrealdb :)&lt;/p&gt;
&lt;p&gt;create a file in same folder `docker-compose.yaml&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;version: &amp;#39;2.3&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;services:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  mysql:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    restart: always&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    image: mariadb:10.7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    container_name: mariadb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ports:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - &amp;quot;3307:3306&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    volumes:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - ./store/:/var/lib/mysql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    mem_limit: 5012m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    networks:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - mysqlnetwork&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    environment:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - MYSQL_ROOT_PASSWORD=root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - MYSQL_DATABASE=local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - MYSQL_USER=root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - MYSQL_PASSWORD=root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - PMA_ARBITRARY=1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - PMA_HOST=mysql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - PMA_PORT=3306&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - PMA_USER=root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - PMA_PASSWORD=root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  mysql-admin:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    restart: always&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    image: phpmyadmin/phpmyadmin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    container_name: mariadb-phpmyadmin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ports:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - &amp;quot;8001:80&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    mem_limit: 5012m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    networks:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - mysqlnetwork&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    depends_on:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      - mysql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    environment:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      PMA_HOST: mysql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      PMA_PORT: 3306&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      PMA_USER: root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      PMA_PASSWORD: root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      UPLOAD_LIMIT: 30000M&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;networks:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  mysqlnetwork:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    driver: bridge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;docker compose up&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wait a while till images are pulled. Then visit &lt;code&gt;http://localhost:8001&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create a database named with &lt;code&gt;surreal&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a table named with &lt;code&gt;person&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PhpMysql by default will ask 4 columns, lets fill them up&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;id - int - 11 auto_increment (AI)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name- varchar 255&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;surname - varchar 255&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;title - varchar 255&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;click save&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;now-we-can-get-our-node-app&quot;&gt;Now we can get our node app&lt;/h3&gt;
&lt;p&gt;Go to the folder where we have &lt;code&gt;app.js&lt;/code&gt; and create new file &lt;code&gt;yapp.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install mariadb --save&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our yapp.js&lt;/p&gt;
&lt;p&gt;Note: by using same math random, we tried to keep the cost same.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const mariadb = require(&amp;#39;mariadb&amp;#39;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const pool = mariadb.createPool({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     host: &amp;#39;127.0.0.1&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     database: &amp;#39;surreal&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     port: 3307,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     user:&amp;#39;root&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     password: &amp;#39;root&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;async function main(){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const conn = await pool.getConnection()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    try{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        const runGreedyQuery = async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            console.time(&amp;quot;dbsave&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            for(let i = 0; i&amp;lt;10000; i++){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                const randomMath = Math.random().toString(36).substr(2, 10)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;               await conn.query(`INSERT INTO person (name,surname,title) values (&amp;#39;myname&amp;#39;, &amp;#39;mysurname&amp;#39;, &amp;#39;${randomMath}&amp;#39;)`);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        await runGreedyQuery().then(r=&amp;gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            console.timeEnd(&amp;quot;dbsave&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }catch(err){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        console.log(err)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    main()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;node yapp.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time elapsed: 11.012s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Record in DB: 10000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Second run;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time elapsed: 9.474s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Record in DB: 20000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There are observable differences in between two insert operations on different databases. Today, under these circumstances SurrealDB won. But that’s not just enough to decide whether to go with one.&lt;/p&gt;
&lt;p&gt;As we have said, we used same environment yet official images of both might be different. May affect the result. Let’s set ourselves a mood of healthy skeptical to figure why we could put ourselves into an adventure by migrating to a new technology.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scaling
Mysql or related forks of it as relational databases is hard to scale. We all know that already. To get yourself a single new column, You have to do your updates on database. Hell of a work and potential human errors. On this aspect, key-value storing like SurrealDB is great but not just it like other non-relation databases. SurrealDB provides a lot of features from relation-databases on its way. Some of those are;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;indexing&quot;&gt;Indexing&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CREATE company:surrealdb SET name = &amp;#39;SurrealDB&amp;#39;, cofounders = [person:tobie, person:jaime];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows for records to be retrieved without any table scans or index scans, solely by the id. Beautiful.&lt;/p&gt;
&lt;h3 id=&quot;no-joins-but-surrealjoins-&quot;&gt;NO JOINS but SurrealJoins :)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  SELECT -&amp;gt;purchased-&amp;gt;product&amp;lt;-purchased&amp;lt;-person-&amp;gt;(purchased WHERE created_at &amp;gt; time::now() - 3w)-&amp;gt;product FROM person:tobie;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Transactions.&lt;/li&gt;
&lt;li&gt;Functions&lt;/li&gt;
&lt;li&gt;Statements&lt;/li&gt;
&lt;li&gt;Operators&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And many more…&lt;/p&gt;
&lt;p&gt;Honestly, i am excited to use it and see all the problems alongside benefits. There are a lot of built-in helpers SurrealDB provides. I belive its worth to take a look at all those promises.&lt;/p&gt;
&lt;h3 id=&quot;give-your-10-minutes-into-documentation&quot;&gt;Give your 10 minutes into documentation&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://surrealdb.com/docs/surrealql&quot;&gt;SurrealQL&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Untill the next post, stay hydrated!&lt;/p&gt;</content:encoded><category>blog</category><category>surrealdb</category><category>mariadb</category><category>benchmark</category><category>docker</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Typescript overview</title><link>https://omeratagun.net/blog/typescript-overview/</link><guid isPermaLink="true">https://omeratagun.net/blog/typescript-overview/</guid><description>Lets dive into a field that light barely comes into</description><pubDate>Sun, 11 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;a-story&quot;&gt;A story&lt;/h1&gt;
&lt;p&gt;Well, actually i was not planning write such thing but considering i got messages from a fellow colleagues,
i decided to expose my feelings about typescript and why type safety of a software is important or when you should start/stop using it.&lt;/p&gt;
&lt;p&gt;Couple of days ago, a colleague of mine complained a little that the current employer of him decided to go with react proptypes. If you have not work with it so far, here is the link&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://reactjs.org/docs/typechecking-with-proptypes.html&quot;&gt;React PropTypes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I was thinking why there could be such decision to make while there is a &lt;code&gt;typescript&lt;/code&gt; as known as superset of javascript and the best tool you could ever use if you are to develop anything on javascript.&lt;/p&gt;
&lt;p&gt;All of a sudden, i dove into thoughts and considered couple of aspects that could make me choose to go with propTypes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Legacy applications&lt;/li&gt;
&lt;li&gt;Relatively less experienced team in terms of writing types. This is maybe the most important trade off to consider.
If your team is about to spend x3 time onto a feature just because there is a complexity of typescript. This may affect your business very badly.&lt;/li&gt;
&lt;li&gt;Maybe its just right for the job. Thoughs on budget, team, time etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have hated entire my life thinking on anything radically. In a world of probabilities, nothing could ever be &lt;code&gt;this is good, or this is bad&lt;/code&gt;.
Even if the wrong choice has been made, as long as there is enough energy and will to do better, you’ll do better :)&lt;/p&gt;
&lt;p&gt;Allright, now lets actually start talking about typescript as we have promised in the title of this blog post.&lt;/p&gt;
&lt;h1 id=&quot;so-why-do-we-need-it-&quot;&gt;So why do we need it ?&lt;/h1&gt;
&lt;p&gt;We do not need to be software engineer to understand this. A very common way to express ourselves to people when we do something together or order something.&lt;/p&gt;
&lt;p&gt;E.g when you ask to your friend whom is on the way to buy a pen that if he/she can buy it for you, what would you say ? &lt;code&gt;i need a pen&lt;/code&gt;. Yeah all right, but how many different types of pens are there ? no need exact quantity but if an item has more than 1 types, you need to specify :)&lt;/p&gt;
&lt;p&gt;It is with variables the same especially on javascript. Imagine a tree, &lt;code&gt;any&lt;/code&gt; would be on top of that and branches would go down to primitive types such as strings, array, object, number. Then those would branch to more detailed types.&lt;/p&gt;
&lt;p&gt;We need to specify for our fellow developers to build something meaningful out of it. Lets give a 2 bare example;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const myCrazyFunction = (a,b) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return a+b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a client of this function ( a fellow collague ) i have no clue what i could ever pass, or what to expect. You may say that &lt;code&gt;allright mate, open this function and see that actually sums 2 numbers, or at least intend to do so.&lt;/code&gt; This was for simple example, but if there is something does more stuff than simply summing 2 numbers. And if my piece of code
is open to human errors, there will always be human errors. My duty is to reduce them as less as possible.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const myCrazyFunction = (a: number, b: number): number =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return a+b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we know that arguments must be a number and this function will return a number. We’d never expect anything else than that. Alright, we are done with this example. Lets make some use case.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;interface HumanProps {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  name: string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  zPosition: number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  armSize: number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  legSize: number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  eyeColor: string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  move: () =&amp;gt; void&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export class Human {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  private human: HumanProps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  constructor(human : HumanProps) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    this.human = human&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  getArmSize(): HumanProps[&amp;#39;armSize&amp;#39;] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return this.human.armSize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  getLegSize(): HumanProps[&amp;#39;legSize&amp;#39;] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return this.human.legSize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  getEyeColor(): HumanProps[&amp;#39;eyeColor&amp;#39;] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return this.human.eyeColor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  move(): HumanProps[&amp;#39;move&amp;#39;] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return this.human.move&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const John = new Human({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  name: &amp;#39;John&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  zPosition: 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  armSize: 1,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  legSize: 2,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  eyeColor: &amp;#39;green&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  move: () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    console.log(&amp;#39;moved&amp;#39;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you instantiate your new human in our case its John, you know exactly what kind of properties a human could have to be created. Its nice right, you exactly know what to expect when and if you use any of these methods.&lt;/p&gt;
&lt;h1 id=&quot;so-far-so-good-any-flame-&quot;&gt;So far so good, any flame ?&lt;/h1&gt;
&lt;p&gt;Nothing is perfect my friend. When u dive into especially generics, things are a little bit messy.&lt;/p&gt;
&lt;p&gt;E.g&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export declare function groupBy&amp;lt;T, K extends T&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(key: (value: T) =&amp;gt; value is K)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;: OperatorFunction&amp;lt;T, GroupedObservable&amp;lt;true, K&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| GroupedObservable&amp;lt;false, Exclude&amp;lt;T, K&amp;gt;&amp;gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not worry its not you. Do not also get me wrong, its okay to write this and it does its job except reading so symbolic and long type will confuse anyone. This is a mix of generic and union types that expects key and value to group by and returns Observable type.&lt;/p&gt;
&lt;p&gt;Lets make some other example that has generic with a condition in it.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;interface SomeClassPropsDetails&amp;lt;TIsAlive&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  isAlive: TIsAlive&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  name: string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  surname: string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  address: string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;interface SomeOtherClassPropDetails&amp;lt;TIsAlive&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  isAlive: TIsAlive&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  name: number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  surname: number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  address: number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export type SomeObjectProps&amp;lt;TIsAlive = boolean&amp;gt; = TIsAlive extends true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ? SomeClassPropsDetails&amp;lt;TIsAlive&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  : SomeOtherClassPropDetails&amp;lt;TIsAlive&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const myObject: SomeObjectProps = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  isAlive: true,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  name: &amp;#39;0&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  surname: &amp;#39;0&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  address: &amp;#39;0&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const yourObject: SomeObjectProps = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  isAlive: false,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  name: 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  surname: 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  address: 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What we have accomplished above is that, we set Type called TIsAlive and defaulted to boolean. We said if TIsAlive is true, then this SomeClassPropsDetails should be referenced, otherwise the other one.&lt;/p&gt;
&lt;p&gt;The difference from the first one is that we can easily read this is type of isAlive property rather typing T only. Because once complexity increases, explicit definitions will always help. We could write T,K,Y and all other symbolic representations, we’d be lost in it.&lt;/p&gt;
&lt;p&gt;Certainly these are pretty starter/basic examples but since you have idea how things can get wild syntactically, any way of simplifying these would help.
If there is a chance to avoid these, should always be taken but otherwise your types will be mainly like this. Most likely as author of this type, at some point you will either forget or do not understand what has been done there.. Since any of types like this will create a lot of problems in terms of reading,
&lt;code&gt;people who is working with you will ask you questions in the end&lt;/code&gt;. Remember, our goal was to wipe out questions. You have asked to your friend if you can get one pen, your friend asked you which type? you had to state wooden, you open this particular conversation prone to human errors right.
&lt;code&gt;All we have ever wanted was to avoid questions to avoid human errors, if we create another while we are solving one, we did nothing :)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;On the other hand, even if your colleague does not understand what is happening there, at least on wrong usage, typescript will throw an error. So we are still a winner in terms of avoiding unknown or potential bugs.&lt;/p&gt;
&lt;h1 id=&quot;compiler-problems&quot;&gt;Compiler problems&lt;/h1&gt;
&lt;p&gt;Oh boy, we said typescript is a superset of javascript right ? So it has its own server to run above your javascript application. has its own compilation and all that &lt;code&gt;ABS&lt;/code&gt;, traversal node trees and stuff. Along the way you will have some compiler issues with other compilers for example ( babel-jest, ts-babel ). You will find yourself in a very different bugs ( yes there are )
and end up with spending most of your time either trying to understand why that does not work or you just simply find another way and move on. So we are still not that cool if we quit using it.&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;If you are writing any kind of javascript code today, it is safe to say that use typescript. You decide at what level it will be helpful and allow you to remain safe. You may as well keep typing &lt;code&gt;any&lt;/code&gt; and do it properly later on. It is all about &lt;code&gt;situation&lt;/code&gt; as we have said many times before :)&lt;/p&gt;
&lt;p&gt;Till the next time, stay hydrated.&lt;/p&gt;</content:encoded><category>blog</category><category>typescript</category><category>javascript</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>How to install rancher</title><link>https://omeratagun.net/blog/how-to-install-rancher/</link><guid isPermaLink="true">https://omeratagun.net/blog/how-to-install-rancher/</guid><description>Lets dive into a field that light barely comes into</description><pubDate>Thu, 11 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have talked in this blog why and when should you use rancher. Now its time to get into how to install and what to be careful about.&lt;/p&gt;
&lt;p&gt;There is not much detailed explanations or sources that you can come out clean. Even though kubernetes is at least 10 years old technology is still relatively new and not much in usage due to its case specific nature.&lt;/p&gt;
&lt;p&gt;Okay now, we have couple options to do so. In previous version of rancher deploying as docker image was not suggested, now they have updated in a way that you can export entire rancher installation if you want to carry your orchestration
into high availability setup.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://rancher.com/docs/rancher/v2.6/en/quick-start-guide/&quot;&gt;Official Rancher Instalation Guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Yes it does exist, but it is generally commonly used providers based. Those are expensive, remember we do not want to pay thousands for some applications that is not needed to.&lt;/p&gt;
&lt;p&gt;So we arrange ourselves a vps server as for to be our control plane,etcd&lt;/p&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Get yourself a some vps linux server ( on Mac it may not work )&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you do not have docker installed in your linux server, get yourself one :)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sudo docker run --privileged -d --restart=unless-stopped -p 80:80 -p 443:443 rancher/rancher&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OR&lt;/p&gt;
&lt;p&gt;You can go with Helm CLI quick start&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://rancher.com/docs/rancher/v2.6/en/quick-start-guide/deployment/quickstart-manual-setup/&quot;&gt;Helm CLI Quickstart&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This installs the k3s server into your machine. Afterwards by using scp you download the kubeconfig file in order to have access to your k3s cluster from your own terminal.&lt;/p&gt;
&lt;p&gt;Then simply via helm repo(sitory) you start to build rancher, namespace for cattlesystem ( rancher’s management system ) cert-manager (for internal and external certificates) charts ( package manager ) also called helm charts has to be installed within this procedure.&lt;/p&gt;
&lt;p&gt;You are ready to go now. Next, when you type ip of your server to your browser, you will be welcomed with rancher’s first log-in&lt;/p&gt;
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-2/2&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://omeratagun.net/static/images/rancherwelcome.png&quot; alt=&quot;Rancherk3s&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Don’t make the guy angry, do as he says :)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To the terminal of yours which connected to the VPS server then;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;type&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker ps&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;you will be witnessing the list of docker images that runs on your machine, rancher asks you to get container id so that you can grab your password, it will look like this&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONTAINER ID   IMAGE             COMMAND           CREATED         STATUS         PORTS                                      NAMES&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4028334c0371   rancher/rancher   &amp;quot;entrypoint.sh&amp;quot;   3 minutes ago   Up 3 minutes   0.0.0.0:80-&amp;gt;80/tcp, 0.0.0.0:443-&amp;gt;443/tcp   focused_banach&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the CONTAINER ID and then type below and enter;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;docker logs 4028334c0371  2&amp;gt;&amp;amp;1 | grep &amp;quot;Bootstrap Password:&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will have message printed to you something like this;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2022/08/11 18:18:06 [INFO] Bootstrap Password: 9jmsvh8ptccgq9kzwhm4gz9bt4nmh7dv228ng42m2xhcst5dcpn6nh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not worry, this is a test server :) enter your password in your browser. Then rancher will ask you to reset this and set by yourself one. Do that too, i just said don’t make the guy angry. Agree the terms which you will never read, continue;&lt;/p&gt;
&lt;h2 id=&quot;first-look&quot;&gt;First look&lt;/h2&gt;
&lt;p&gt;On the welcome screen, you will be flowing with information of your cluster, there will be one named local as default when you click that, you will be witnessing your cluster details. System resources, how much used, how much reserved etc.&lt;/p&gt;
&lt;p&gt;Let’s deploy something that actually works. Depends on your personality, i will give you a 2 options. First is to deploy something via &lt;strong&gt;Workload -&amp;gt; Deployments&lt;/strong&gt; OR &lt;strong&gt;Apps&lt;/strong&gt; section to see how all these stuff combined.&lt;/p&gt;
&lt;h2 id=&quot;workloads&quot;&gt;Workloads&lt;/h2&gt;
&lt;p&gt;Most of the time, you will need this place to bring your images and install into your cluster. Lets create one&lt;/p&gt;
&lt;p&gt;If you can’t see the image below, copy the link of the image to get bigger version of it&lt;/p&gt;
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-2/2&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://omeratagun.net/static/images/rancherdeployments.png&quot; alt=&quot;Rancherk3s&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I am going with mariadb deployment. Let’s name our deployment in name* field which is needed. Say it like &lt;code&gt;mariadb-deployment&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;On the Container image, we say get us version 10.7 which is &lt;code&gt;mariadb:10.7&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Not yet, we still have to tell which ports to look at. Below image, you will see ports Add two of them and say leave it as &lt;code&gt;Do not create a service&lt;/code&gt; and name them as you are pleased to. Private container port for both is 3306 ( default mariadb, mysql ports )&lt;/p&gt;
&lt;p&gt;Now we need to set mysql root password so we can access to it. To achieve that, Go below and find &lt;code&gt;Environment variables&lt;/code&gt; and add one as &lt;code&gt;Key/Value pair&lt;/code&gt; variable name should be &lt;code&gt;MYSQL_ROOT_PASSWORD&lt;/code&gt; and value is your password so set one. I just typed &lt;code&gt;myamazingpassword&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-2/2&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://omeratagun.net/static/images/ranchermariadbdeployment.png&quot; alt=&quot;Rancherk3s&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Congratulations, you now have successfully deployed your mariadb instance. Surely this is not the end, but it’s a nice beginning. There is a lot more to do such as creating yourself a persistent storage in order to assure even if your mariadb deployment is gone, your data remains the same.&lt;/p&gt;
&lt;p&gt;Go to your workload on the left menu, you will see mariadb-deployment is active ( wait for a while if not ready yet ) on far right, you will see 3 dots as dropdown menu, click that and execute shell. This will allow you to get sh access into your container. So you can see if your deployment is actually running.&lt;/p&gt;
&lt;p&gt;Just to test its there, write &lt;code&gt;mariadb&lt;/code&gt; or &lt;code&gt;mysql&lt;/code&gt; and you will see response as below. No i will not teach you how to create DB and stuff, that’s entirely another topic :)&lt;/p&gt;
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-2/2&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://omeratagun.net/static/images/ranchermariainstance.png&quot; alt=&quot;Rancherk3s&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Next maybe we can continue with persistent storages using Longhorn. Till then stay hydrated :)&lt;/p&gt;</content:encoded><category>blog</category><category>rancher</category><category>k3s</category><category>cluster</category><category>docker</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Laravel octane roadrunner benchmark</title><link>https://omeratagun.net/blog/laravel-octane-benchmark/</link><guid isPermaLink="true">https://omeratagun.net/blog/laravel-octane-benchmark/</guid><description>You will be suprised</description><pubDate>Sat, 06 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Previously i have explained the reason of using kubernetes clusters. While all the work going on, with my spared time i got myself into testing PHP-FPM and Octane with roadrunner(GO one)&lt;/p&gt;
&lt;h2 id=&quot;problem-&quot;&gt;Problem ?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I have 6 VPS servers 1 as control plane/etcd and 5 worker nodes in k3s/rancher cluster. I have prepared a new version of eDominations into my DockerFile. Surely started with PHP-FPM as like all other standart php servers.
Image has been built, pushed to dockerhub private repository. Using MariaDB and Redis deployment in the cluster, all config assigned now time to run some Apache benchmark tests.&lt;/p&gt;
&lt;p&gt;Layer 4 load balancer with 5 node Laravel deployment ( 5 instances of PHP server has an access to redis server inside the cluster read/write permissions)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Laravel with PHP-FPM&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ab -n 1000 -c 250 -k -H &amp;quot;Accept-encoding: gzip, deflate&amp;quot; -rk https://xxx.xxx.com/api/toparticles&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Document Path:          /api/toparticles/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Document Length:        884 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Concurrency Level:      250&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Time taken for tests:   34.705 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Complete requests:      1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Failed requests:        457&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; (Connect: 0, Receive: 0, Length: 457, Exceptions: 0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Non-2xx responses:      457&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Keep-Alive requests:    0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Total transferred:      4345222 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; HTML transferred:       4026812 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Requests per second:    28.81 [#/sec] (mean)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Time per request:       8676.225 [ms] (mean)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Time per request:       34.705 [ms] (mean, across all concurrent requests)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Transfer rate:          122.27 [Kbytes/sec] received&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;When this api endpoint called, Laravel with PHP-fpm instance connects to redis-server inside our cluster ( private network so latency is minimal as possible ). As you can see we were able to response 28.81 Requests per second.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was a bit of disappointment for me. I have started thinking that even if all the nodes are in same datacenter location ( 0.05 ms latency ) cluster somehow kills the entire performance but then i have decided to change the approach. Hence, the problem has been created :)&lt;/p&gt;
&lt;h2 id=&quot;different-approach&quot;&gt;Different approach!&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;We just said we need to work onto something different. I have not used Laravel’s octane in production but since i am building new version of eDominations, why not? i am brave enough to experiment things while thinking any aspect that could go wrong. I found myself building docker image for octane.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Consider that each VPS server of mine has shared 4vCPu 8GB ram and 50GB nvme. It was the same when we were testing with PHP-FPM&lt;/p&gt;
&lt;p&gt;Quick look to &lt;a href=&quot;https://laravel.com/docs/9.x/octane&quot;&gt;Octane&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Once i figured the requirements, rest was to build docker images and see if all going great. Same servers, same cluster, same docker image with additional installations in it. Then the results were amazing. In a way that you would never want to go back using FPM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Laravel-Octane with Roadrunner&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ab -n 1000 -c 250 -k -H &amp;quot;Accept-encoding: gzip, deflate&amp;quot; -rk https://xxx.xxx.com/api/toparticles&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Document Path:          /api/toparticles&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Document Length:        884 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Concurrency Level:      250&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time taken for tests:   4.159 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Complete requests:      1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Failed requests:        638&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   (Connect: 0, Receive: 0, Length: 638, Exceptions: 0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Non-2xx responses:      638&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Keep-Alive requests:    362&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Total transferred:      4857397 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HTML transferred:       4546758 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Requests per second:    240.46 [#/sec] (mean)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time per request:       1039.667 [ms] (mean)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time per request:       4.159 [ms] (mean, across all concurrent requests)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Transfer rate:          1140.64 [Kbytes/sec] received&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Almost 10x times. We are friends here call it 10 times.&lt;/p&gt;
&lt;h2 id=&quot;things-to-consider&quot;&gt;Things to consider&lt;/h2&gt;
&lt;p&gt;Due to its nature, octane with roadrunner spawns workers based on the cpu cores you have available. You can increase or decrease these with the configuration. If your application did not concerned potential memory leaks, you have option to respawn processes after given amount of requests they serve.
This explicitly makes your application to adapt easier to this change.&lt;/p&gt;
&lt;p&gt;Until the next post, stay hydrated!&lt;/p&gt;</content:encoded><category>blog</category><category>laravel</category><category>octane</category><category>laravel octane</category><category>docker</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>When and Why should you use rancher</title><link>https://omeratagun.net/blog/when-and-why-should-you-use-rancher/</link><guid isPermaLink="true">https://omeratagun.net/blog/when-and-why-should-you-use-rancher/</guid><description>Lets discover when we need a rancher and understand why we need it ?</description><pubDate>Thu, 21 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Problems of scaling is known issue for pretty long time, its painful and open to problems generously. This is exactly when k8s/k3s or orchestration software like rancher comes in handy.&lt;/p&gt;
&lt;h2 id=&quot;why-&quot;&gt;Why ?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Because you need to scale your application due to high traffic. Yes just because of that. But are there any comforts that i could use of even if did not need scaling ? Answer is yes but i suggest not to because it takes time and resources for such small size application.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;when-&quot;&gt;When ?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Lets examine a situation of two different application that requires different deployment.&lt;/p&gt;
&lt;h3 id=&quot;deployment-scenario-for-static-html-page&quot;&gt;Deployment scenario for &lt;strong&gt;Static HTML page.&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Imagine you have a landing page for your client that will have an information about the company, its team and contact information. Data will highly likely never change and even if it does, you might spend 5 minutes to change phone number and deploy back again.&lt;/p&gt;
&lt;p&gt;If this landing page is for small size company, safe to think that there will be visitors once a while &lt;strong&gt;let’s assume once a day&lt;/strong&gt;, for this you should never bother yourself with scaling problems because you do not have any. It is just fine to use platforms like netlify or some budget resources VPS to get job done&lt;/p&gt;
&lt;p&gt;What if this company got popular and demands bigger resources such as process power, ram, bigger network and so. Yes this is the exact moment, you have to start thinking either scaling vertically or horizontally.&lt;/p&gt;
&lt;p&gt;First, you would try to get bigger VPS but then after a short time, you would get bigger one, then again and again. You already got the idea how that is a problem. Each time you have to set up required pieces of software to run your application. E.g some linux distro, nginx/treafik/apache, firewall.&lt;/p&gt;
&lt;p&gt;If you did dockerized your deployment, yes its relatively lesser time but how can we be so sure the hardware we have picked will be fine for the load and what if we lose the traffic? we’d be keep paying the bigger hardware bills. Which is a waste of business resources.&lt;/p&gt;
&lt;p&gt;All right, we got the idea, we need something that can do all this fuss without we are present and having a domain knowledge at any moment. That is the exact job description for &lt;strong&gt;Kubernetes and docker&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;What is &lt;strong&gt;Docker&lt;/strong&gt; now? where that thing came from? There are a lot of resources explains that, we skip this part. Yet, it is simply a container that contains your application with its requirements.&lt;/p&gt;
&lt;p&gt;So then, what is &lt;strong&gt;Kubernetes&lt;/strong&gt;? it is a manager for your &lt;strong&gt;Docker&lt;/strong&gt; containers. Somebody got to manage and make them communicate in between each other so that will not be you, now its time to be happy.&lt;/p&gt;
&lt;p&gt;Now, back to &lt;strong&gt;when and why&lt;/strong&gt; we are using kubernetes and rancher.&lt;/p&gt;
&lt;p&gt;We can simply scale the application of ours as we mentioned above in a neat and automated way. A &lt;strong&gt;very basic&lt;/strong&gt; graphical presentation could help you understand&lt;/p&gt;
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-1/2&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://omeratagun.net/static/images/rancher.png&quot; alt=&quot;Rancherk3s&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;What we have here is when a user requests our service/application we have a load balancer that forwards this request to related service and this service gets the information/data from the Worker/Server and related pods and shows to our user. Considering our static html does not require anything else, we can scale this to any amount of requests we can have.&lt;/p&gt;
&lt;p&gt;Whenever we need more servers, we can just add one more and all will be done automatically by our orchestration. Good part is that we can add another services/applications that uses same resources.&lt;/p&gt;
&lt;h3 id=&quot;deployment-scenario-for-multiple-services&quot;&gt;Deployment scenario for &lt;strong&gt;Multiple services&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Above example was relatively easy to handle and do the cool stuff. But now lets say we have a service that needs to communicate other service, what then ? Well that is also pretty easy. Especially in &lt;strong&gt;Rancher&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;E.g we have 2 services one is NodeJS application and other is mongodb. These two have to communicate/transmit data in order to process requests and response back something we wanted to.&lt;/p&gt;
&lt;p&gt;You would have each service as different deployments and connect them within the &lt;strong&gt;cluster&lt;/strong&gt; itself, and they would do their job. Visual presentation below;&lt;/p&gt;
 
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-1/2&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://omeratagun.net/static/images/nodemongo.png&quot; alt=&quot;Rancherk3s&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;When a service requires to communicate others, we simply state in our environment and say that “when you need a mongodb connection, look at in which private ip/pod/worker/server its exposed and go there”. Cool stuff right ?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-other-cool-stuff-is-in-there-&quot;&gt;What other cool stuff is in there ?&lt;/h2&gt;
&lt;p&gt;Well a lot. Rancher was doing thing differently in its early versions like v1x such as trying to provide helm charts ( package manager for kubernetes ) by themselves and maintain them. This was too much of a problem/work to keep them up and provide support for it. They have released some of duties away and as you can see, a lot more new stuff in short amount of time is in action!&lt;/p&gt;
&lt;p&gt;I’d like to mention some of them such as;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Auto-scaling with your favorite service providers such as AWS, GCloud, linode&lt;/li&gt;
&lt;li&gt;Load balancer level SSL encryption ( so your apps does not concern any of this )&lt;/li&gt;
&lt;li&gt;Horizontal pod autoscaler - depends on your metrics, scale your stuff horizontally, thats the cool stuff.&lt;/li&gt;
&lt;li&gt;Very easy persistent storage usage with &lt;strong&gt;&lt;a href=&quot;https://longhorn.io&quot;&gt;Longhorn&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div class=&quot;flex flex-wrap -mx-2 overflow-hidden xl:-mx-2&quot;&gt;&lt;div class=&quot;my-1 px-2 w-full overflow-hidden xl:my-1 xl:px-2 xl:w-1/2&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://omeratagun.net/static/images/normalhuman.png&quot; alt=&quot;Rancherk3s&quot;/&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Easy multiple cluster management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This pretty much sums up when and why should you use rancher. No matter what i said, you may find your own reason and experiment it on the way. Most likely i have missed some things to mention but if you were to talk and exchange knowledge, reach me out!.&lt;/p&gt;
&lt;p&gt;Until the next post stay hydrated&lt;/p&gt;</content:encoded><category>blog</category><category>kubernetes</category><category>rancher</category><category>k3s</category><category>docker</category><author>omratagn@gmail.com (Omer Atagun)</author></item><item><title>Not just hello world</title><link>https://omeratagun.net/blog/not-just-hello-world/</link><guid isPermaLink="true">https://omeratagun.net/blog/not-just-hello-world/</guid><description>Another initial blog post to be forgotten in time. Even though this blog will be mainly about my journey in tech/science, i felt need introducing my blog under this post</description><pubDate>Sun, 03 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This was a thing for sometime in my mind so that i could use my own documentation in manner of blog and help others to solve their problems ( presumably they have the same ones, how cant they? )&lt;/p&gt;
&lt;p&gt;Finally i found some spare time and done this bad boy to deploy on my own k3s cluster. This is a pretty neat next js application that has standalone approach without in need of any relational databases.&lt;/p&gt;
&lt;p&gt;I mean why would you need it if you are to write your own stuff and no others will do on a web interface? you can solve with simple configuration as they are objects and map them through. Whenever you want to publish new blog post, build your image and deploy, thats pretty much it.&lt;/p&gt;
&lt;p&gt;This way, we can scale this application however we wish to. No persistent storages, no sql instance at all. What i am about to do is making a docker image for this, publish to my private docker repository. create deployment in rancher/k3s and scale it to &lt;code&gt;N&lt;/code&gt; amount with beautiful layer 4 load balancer.&lt;/p&gt;
&lt;p&gt;Worry not my friend, i will be explaining all the process along the way in next post :)&lt;/p&gt;
&lt;p&gt;See you soon.&lt;/p&gt;</content:encoded><category>blog</category><category>introduction</category><category>rancher</category><category>nextjs</category><category>k3s</category><author>omratagn@gmail.com (Omer Atagun)</author></item></channel></rss>