<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stelios</title>
    <description>The latest articles on DEV Community by Stelios (@interstelios).</description>
    <link>https://dev.to/interstelios</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F111675%2F6de66fec-1a6e-4c51-89b5-e51732949884.webp</url>
      <title>DEV Community: Stelios</title>
      <link>https://dev.to/interstelios</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/interstelios"/>
    <language>en</language>
    <item>
      <title>Surface Tension of Software: why systems hold together</title>
      <dc:creator>Stelios</dc:creator>
      <pubDate>Sun, 07 Dec 2025 14:00:56 +0000</pubDate>
      <link>https://dev.to/interstelios/surface-tension-of-software-why-systems-hold-together-5aka</link>
      <guid>https://dev.to/interstelios/surface-tension-of-software-why-systems-hold-together-5aka</guid>
      <description>&lt;p&gt;If you press your finger against water, it pushes back. That invisible resistance, surface tension, keeps the liquid whole even when disturbed.&lt;/p&gt;

&lt;p&gt;Good software has something like it. Some systems hold together when you change them; others leak at the slightest touch. The difference lies in integrity — the way a system manages its side effects without losing its shape.&lt;/p&gt;

&lt;p&gt;I've seen codebases that felt strangely calm, where every possible state meant something real and nothing arbitrary could slip in. Others allowed nonsense to exist, and from there, entropy spread quietly like cracks beneath paint.&lt;/p&gt;

&lt;p&gt;Type systems, invariants, and boundaries exist to make meaning explicit. They define where things start and stop — what’s allowed, and what isn’t. Without that structure, logic turns soft; assumptions spread, and the system eventually folds under its own ambiguity.&lt;/p&gt;

&lt;p&gt;Systems stay whole when their structure insists on coherence: clear boundaries, honest interfaces, consistent language.&lt;br&gt;
Each adds its own gravity, and together they make a world that holds. Stability isn’t declared; it emerges from the sum of small, consistent forces.&lt;/p&gt;

&lt;p&gt;Constraint-driven design makes that gravity visible. In software, these laws have names: purity, immutability, idempotence, transparency, composability. They’re the physics that keep a system in orbit.&lt;/p&gt;

&lt;p&gt;Pure functions return the same output for the same input, with no hidden effects. Immutable data can't be changed after creation, only transformed. Idempotent operations produce the same result no matter how many times you apply them. These aren't academic exercises — they're physics that prevent the impossible.&lt;/p&gt;

&lt;p&gt;But let one careless change skip a step and the world tears.&lt;/p&gt;

&lt;p&gt;Consider a UI that fetches user data. Without tension, it leaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;UserProfile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does it mean when loading is false, error is Some, and data is also Some? The type allows nonsense. You write defensive checks everywhere. Every render must guess which combination is real.&lt;/p&gt;

&lt;p&gt;Now give it shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;UserProfile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;Loaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The impossible states vanish. You can't be loading and failed. Pattern matching forces you to handle every valid case, and only those. The type system becomes the membrane — it holds the shape.&lt;/p&gt;

&lt;p&gt;In well-shaped systems, nonsense simply cannot exist because the universe of the program doesn't contain it. You don't defend against the impossible. You design a world where the impossible has no syntax.&lt;/p&gt;

&lt;p&gt;When those laws are clear, surface tension appears on its own — you feel it when refactors stop rippling outward, when a change bends without breaking, when boundaries push back just enough to preserve meaning.&lt;/p&gt;

&lt;p&gt;Good patterns and abstractions behave like membranes. They don't restrain motion; they guide it. They contain side effects the way water's surface holds its ripples — movement without spillage, energy without collapse. Parsing, typing, composition: these are the laws of motion that let a system stay whole when disturbed.&lt;/p&gt;

&lt;p&gt;There's an old engineering instinct that says, "We'll handle it later if it happens." But in coherent systems, it simply can't happen. You can only move through valid states, and that constraint is what makes motion possible at all.&lt;/p&gt;

&lt;p&gt;But tension has its limits. Too much and water hardens into ice — flawless, unmoving, lifeless. Software can freeze the same way, becoming so rigid it forgets to flow. Balance lives somewhere between order and change, between holding on and letting go.&lt;/p&gt;

&lt;p&gt;The best systems live there, in that delicate balance where structure meets freedom. And perhaps that's where the worlds of hackers and painters quietly meet, each shaping their medium until form and motion become one.&lt;/p&gt;

&lt;p&gt;That precise balance is the true art of code.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Further reading:&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Rich Hickey’s &lt;a href="https://www.infoq.com/presentations/Simple-Made-Easy/" rel="noopener noreferrer"&gt;&lt;em&gt;Simple Made Easy&lt;/em&gt;&lt;/a&gt; — a classic talk on why&lt;br&gt;
simplicity comes from separation, not convenience.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>engineering</category>
      <category>softwarequality</category>
      <category>programming</category>
    </item>
    <item>
      <title>Systems As Mirrors</title>
      <dc:creator>Stelios</dc:creator>
      <pubDate>Sun, 12 Oct 2025 06:14:34 +0000</pubDate>
      <link>https://dev.to/interstelios/systems-as-mirrors-3g5n</link>
      <guid>https://dev.to/interstelios/systems-as-mirrors-3g5n</guid>
      <description>&lt;p&gt;There’s something quietly unsettling about &lt;a href="https://www.wikiwand.com/en/articles/Conway%27s_law" rel="noopener noreferrer"&gt;Conway’s Law&lt;/a&gt;. Most people who’ve worked in software long enough can sense its truth. What unsettles is what it reveals when you sit with it.&lt;/p&gt;

&lt;p&gt;The law definition, in its original form, is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Organizations which design systems (in the broad sense used here) are constrained to produce designs which are copies of the communication structures of these organizations.&lt;/p&gt;

&lt;p&gt;— Melvin E. Conway, &lt;em&gt;How Do Committees Invent?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first, it seems structural. Org charts become system diagrams. Split your teams by department, and the system splits the same way. But over time, the reflection deepens. The system doesn’t only mirror who talks to whom — it starts echoing what’s understood, and what’s avoided.&lt;/p&gt;

&lt;p&gt;The most confusing systems I’ve seen don’t reflect complexity. They reflect ambiguity.&lt;/p&gt;

&lt;p&gt;You see it in familiar places. One service checks a user’s admin flag. Another checks something else entirely. A third overrides both. A field like “approved” means one thing to scoring, another to compliance, and something looser to product. The code bends around uncertainty. But what it adapts to isn’t complexity — it’s a missing decision.&lt;/p&gt;

&lt;p&gt;This gets worse when teams try to support everything. A lending flow starts with a clean path. Then you add freelancers, students, returning users. You don’t create new flows. You add flags: &lt;code&gt;hasPayslip&lt;/code&gt;, &lt;code&gt;isStudent&lt;/code&gt;, &lt;code&gt;incomeSource&lt;/code&gt;. Logic branches. Models fork. Scenarios pile up.&lt;/p&gt;

&lt;p&gt;Each rule makes sense in isolation. But together, they blur the center. “ApplicationApproved” starts meaning different things depending on the path taken. Eventually, no one can say what it means — only how it behaves under certain conditions.&lt;/p&gt;

&lt;p&gt;The system still works. But its meaning doesn’t.&lt;/p&gt;

&lt;p&gt;This is where outcome-driven modeling helps. &lt;a href="https://www.wikiwand.com/en/articles/Outcome-Driven_Innovation" rel="noopener noreferrer"&gt;Jobs To Be Done&lt;/a&gt; is one approach. It shifts the focus from who the user is to what they’re trying to accomplish. It asks what outcome they’re hiring the system to deliver — and what tradeoffs they’re willing to make.&lt;/p&gt;

&lt;p&gt;A persona tells you the user is a freelancer who prefers mobile and has irregular income. The job tells you: access a short-term loan using recent invoices. Suddenly, the model sharpens. Income is about pattern, not payslips. Risk is about volatility. Rejection isn’t the fallback — manual review is.&lt;/p&gt;

&lt;p&gt;When you model from outcome, the system knows what it's trying to do.&lt;/p&gt;

&lt;p&gt;This is also where domain boundaries matter. &lt;a href="https://martinfowler.com/bliki/DomainDrivenDesign.html" rel="noopener noreferrer"&gt;Domain-Driven Design&lt;/a&gt; introduces the idea of &lt;a href="https://martinfowler.com/bliki/BoundedContext.html" rel="noopener noreferrer"&gt;bounded contexts&lt;/a&gt;: each part of the system defines its own meaning. Duplication isn’t always a mistake — it’s often how clarity is preserved. Reuse works best when meaning aligns.&lt;/p&gt;

&lt;p&gt;Take the classic &lt;code&gt;User&lt;/code&gt; model. In KYC, it means identity: documents, name, status. In Scoring, it means applicant: income, employment, credit history. In Auth, it means account: credentials, sessions, MFA. Reusing the ID is fine. Reusing the model leads to something brittle: dozens of nullable fields, conflicting assumptions, tangled dependencies.&lt;/p&gt;

&lt;p&gt;Some say duplication is always bad. But that’s a narrow view. It depends on what’s being repeated. Echoing confusion spreads it. But repeating meaning reinforces clarity. Duplication isn’t the enemy — imprecision is.&lt;/p&gt;

&lt;p&gt;Once each model speaks a single truth, everything downstream improves. The backend is easier to debug. The frontend becomes more predictable. Changes get safer. Teams stop negotiating the meaning of basic terms and start delivering again.&lt;/p&gt;

&lt;p&gt;And that brings us back to the mirror.&lt;/p&gt;

&lt;p&gt;Conway’s Law tells us systems mirror their makers. But reflections go further than structure. They echo our indecision, our shortcuts, the choices we didn’t make.&lt;/p&gt;

&lt;p&gt;So the real question is:&lt;br&gt;&lt;br&gt;
&lt;em&gt;If every system is a mirror — what part of ourselves are we willing to see?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>conway</category>
      <category>ddd</category>
      <category>decisionmaking</category>
    </item>
    <item>
      <title>Too Many Chefs, One Kitchen</title>
      <dc:creator>Stelios</dc:creator>
      <pubDate>Sun, 08 Jun 2025 07:45:03 +0000</pubDate>
      <link>https://dev.to/interstelios/too-many-chefs-one-kitchen-19id</link>
      <guid>https://dev.to/interstelios/too-many-chefs-one-kitchen-19id</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu0nfyo7sftennsgufse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu0nfyo7sftennsgufse.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Most teams don’t start with architecture problems. They start by building. Fast. A small product, a tight team, a system that does just enough. And for a while, that’s the right choice. You’re shipping. You’re learning. You’re moving.&lt;/p&gt;

&lt;p&gt;But at some point, usually when the business starts to grow, something shifts. Releases become slower. Teams start stepping on each other. And what once felt nimble now feels stuck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The instinctive answer is technical debt — maybe the system just needs refactoring. Or maybe it’s a process problem. But often, the real issue is more structural.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your architecture no longer matches your organization’s needs.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A common sign of this mismatch is &lt;strong&gt;deployment friction&lt;/strong&gt;. One team wants to ship a small feature. But they have to wait. Wait for another team to finish testing. Wait for someone else’s changes to stabilize. Wait because even a tiny change means releasing everything.&lt;/p&gt;

&lt;p&gt;The system works — technically. But it’s like sharing one kitchen with five different chefs.&lt;br&gt;&lt;br&gt;
One team’s baking. Another’s cleaning fish. Someone else just spilled oil on the floor.&lt;br&gt;&lt;br&gt;
You can coordinate. You can take turns. But you can’t move freely. And eventually, the kitchen becomes the bottleneck.&lt;/p&gt;

&lt;p&gt;This is what monoliths often feel like: &lt;strong&gt;centralized&lt;/strong&gt;, &lt;strong&gt;coupled&lt;/strong&gt;, and &lt;strong&gt;fragile under pressure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Microservices don’t solve everything, but they restructure the problem. By giving each team their own “station,” they reduce the coordination tax. Teams can deploy independently. They can fail independently. They can learn faster, without always waiting for the rest of the kitchen to finish cooking.&lt;/p&gt;

&lt;p&gt;That doesn’t mean microservices are always better. Just that at a certain scale — of product, of people, of change — they become a way to &lt;strong&gt;align architecture with the shape of the organization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What follows is a simple example — not to show how to “do microservices,” but to make clear what kind of friction they solve, and why it starts to matter more as teams grow. Think of it as a zoom-in on what happens when structure starts slowing teams down — not because of failure, but because of how things are bundled.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;Imagine a SaaS platform offering &lt;strong&gt;payment processing and loyalty features&lt;/strong&gt; for small businesses. It’s been growing steadily, and the codebase — a monolith — has carried the company through its early traction. Releases are coordinated, large, and slow, but manageable.&lt;/p&gt;

&lt;p&gt;Then a new opportunity arises.&lt;/p&gt;

&lt;p&gt;The product team identifies a small pricing optimization — tweaking loyalty point accrual rules to encourage repeat purchases. It only affects &lt;em&gt;existing users&lt;/em&gt;. Forecast models suggest it could increase &lt;strong&gt;annual revenue by 2%&lt;/strong&gt;. It’s a focused change, with minimal risk, and the team wants to run a fast A/B test to validate it.&lt;/p&gt;

&lt;p&gt;But it can’t ship.&lt;/p&gt;

&lt;p&gt;It’s bundled into the same release as a compliance update — one that applies exclusively to &lt;em&gt;new customer onboarding&lt;/em&gt; and affects shared reporting logic. The loyalty change, meanwhile, is for &lt;strong&gt;existing users&lt;/strong&gt;. The two have nothing to do with each other, but because they live in the same monolith, they move together. &lt;em&gt;You just want to add a bit of seasoning and send the plate out. But the kitchen won’t let anything leave until the whole menu is rewritten.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So the loyalty experiment waits. And each day of delay costs more than just projected revenue — it delays validation. No test runs. No feedback arrives. The team can’t iterate. &lt;em&gt;Momentum slows.&lt;/em&gt; The problem isn’t the work. It’s the structure that forces unrelated changes to move together.&lt;/p&gt;

&lt;p&gt;Microservices would let the loyalty team ship on their own terms. They could release early, run the test, learn what works, and improve the feature before the week’s over. And perhaps more importantly — they’d own the outcome from end to end.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Architecture changes behavior.&lt;/strong&gt;  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When teams can move without waiting, they start testing more, listening sooner, and improving faster. Not because of a mandate — but because the system allows it.&lt;br&gt;&lt;br&gt;
But that freedom doesn’t come for free.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;It’s easy to see microservices as the fix: split the kitchen, give each team their own space, and let them move fast. And when done right, it works.&lt;/p&gt;

&lt;p&gt;But it also comes with a new kind of burden.&lt;/p&gt;

&lt;p&gt;Every kitchen now needs its own tools, its own cleanup, its own supply of ingredients. And to make something great, those ingredients need to be fresh, high-quality, and well-managed — and that adds not just effort, but real operational cost.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Microservices give you autonomy — but only if you're mature enough to handle it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means good testing. Clear ownership. Reliable observability. Solid deployment pipelines. If any of those fall short, the service breaks — or worse, breaks others silently.&lt;/p&gt;

&lt;p&gt;Monoliths concentrate complexity in one place. Microservices distribute it.&lt;br&gt;&lt;br&gt;
Which means &lt;strong&gt;you don’t reduce complexity — you just move it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the teams aren’t ready — if the ingredients aren’t right — you won’t get autonomy. You’ll get inconsistency. Drift. And a lot of mess to clean up.&lt;/p&gt;

&lt;p&gt;And so the question becomes: &lt;strong&gt;when&lt;/strong&gt; do you make the shift?&lt;/p&gt;

&lt;p&gt;You don’t need five kitchens on day one.&lt;br&gt;&lt;br&gt;
A single shared space works just fine — until it doesn’t.&lt;/p&gt;

&lt;p&gt;Microservices aren’t a milestone to reach.&lt;br&gt;&lt;br&gt;
They’re a response to something felt: friction, delays, coordination overhead that’s no longer manageable. If your teams are shipping smoothly, if ownership is clear, if feedback loops are tight — stay where you are.&lt;/p&gt;

&lt;p&gt;But when the line to cook is longer than the cooking itself — when teams spend more time waiting than building — it’s worth asking whether the kitchen still fits.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Structure should serve the pace and shape of your product.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;If it starts slowing you down, it’s time to reshape it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The best teams aren’t just fast.&lt;br&gt;&lt;br&gt;
They’re intentional about the systems they build around them — not just in code, but in how they move.&lt;/p&gt;

&lt;p&gt;Architecture is just one part of that system. But it’s powerful.&lt;br&gt;&lt;br&gt;
Because when it’s right, it doesn’t just unblock delivery.&lt;br&gt;&lt;br&gt;
It &lt;strong&gt;unlocks behavior&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>architecture</category>
      <category>dx</category>
      <category>delivery</category>
    </item>
  </channel>
</rss>
