<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://avrebarra.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://avrebarra.github.io/" rel="alternate" type="text/html" /><updated>2026-10-25T15:11:18+00:00</updated><id>https://avrebarra.github.io/feed.xml</id><title type="html">Avre Barra - Software Engineer | Go, Systems &amp;amp; Resilience</title><subtitle>A personal blog by Avre Barra covering software engineering, Go, and thoughts on building systems.</subtitle><author><name>Avre Barra</name></author><entry><title type="html">React Component Maintainability Cookbook</title><link href="https://avrebarra.github.io/react-component-maintainability-cookbook/" rel="alternate" type="text/html" title="React Component Maintainability Cookbook" /><published>2026-08-06T00:00:00+00:00</published><updated>2026-08-06T00:00:00+00:00</updated><id>https://avrebarra.github.io/react-component-maintainability-cookbook</id><content type="html" xml:base="https://avrebarra.github.io/react-component-maintainability-cookbook/"><![CDATA[<p>This is my cookbook. When a component starts feeling hard to read or hard to maintain, I open this, find the symptom in the glossary, and follow the recipe.</p>

<h2 id="1-the-standard">1. The standard</h2>

<p>Readable is a property of today. Maintainable is a property of next week, next person, next feature. Two different questions: “Can I read this?” and “Is it safe to change this?” Judge every component by the second one.</p>

<h2 id="2-triage---run-this-first">2. Triage - run this first</h2>

<p><strong>Symptom.</strong> You can’t list what the file does in one sentence each. Handlers reference each other’s state.</p>

<p><strong>Diagnosis.</strong> Run these in order. First yes wins.</p>

<ol>
  <li>Same markup repeated? → extract a component.</li>
  <li>Logic that doesn’t touch React? → extract a pure function.</li>
  <li>Handlers tangling each other’s state? → extract a hook.</li>
  <li>Boolean modes everywhere? → variants or composition.</li>
  <li>States that can’t happen, possible? → union types or a reducer.</li>
  <li>Data drilled through layers? → compose, then context.</li>
  <li>Slow renders? → memo, split, virtualize.</li>
</ol>

<h2 id="3-techniques---how-to-apply-fixes">3. Techniques - how to apply fixes</h2>

<h3 id="the-fix-ladder">The fix ladder</h3>

<p>Choose the lowest rung that works: function → component → hook → reducer → context → library.</p>

<p>Before writing any hook, run the test: does this touch React state, effects, or refs? No → it’s a plain function. Most logic doesn’t touch React at all.</p>

<h3 id="the-extraction-pass">The extraction pass</h3>

<p>Use this whenever you wall off a feature.</p>

<ol>
  <li>Name the features.</li>
  <li>Find the islands: features whose state no one else touches.</li>
  <li>Extract the islands first. Cheapest, zero risk.</li>
  <li>One commit path: a value and its mirrors get written in one function, not ten.</li>
  <li>Refs have one owner: two features touching one ref means extract one of them.</li>
</ol>

<h2 id="4-the-recipes">4. The recipes</h2>

<h3 id="size-and-structure">Size and structure</h3>

<h4 id="god-component">God component</h4>

<p><strong>Symptom.</strong> Many features piled into one file. The component has a long name for what is really a collection of unrelated behaviors.</p>

<p><strong>Fix.</strong> Name the features. Extract the islands first, cheapest first. Do not extract features that aren’t tangled. Walls around empty rooms are clutter. Use the extraction pass.</p>

<p><strong>Found.</strong> AnnotationInput: 8 features in 395 lines. The extraction is in the ref soup recipe.</p>

<h4 id="mixed-layers">Mixed layers</h4>

<p><strong>Symptom.</strong> DOM math, state logic, and JSX all in one flat function. The hardest code in the file is buried mid-function between useState calls.</p>

<p><strong>Fix.</strong> Extract logic that doesn’t touch React into pure function modules. Cheap, testable, zero overhead.</p>

<p><strong>Found.</strong> The caret-mirror measurement (caretContentTop, caretEdgeLines) was pure DOM math sitting inside AnnotationInput. It was already functions. The fix was moving it out with its feature, not converting it to a hook.</p>

<h4 id="duplication">Duplication</h4>

<p><strong>Symptom.</strong> Two files that are 95% identical.</p>

<p><strong>Fix.</strong> One component, one variant prop. Check the sibling files first, always. The cheapest find in any codebase is duplication.</p>

<p><strong>Found.</strong> SectionHeaderBroken and SectionHeaderRemoved, 95 lines each, 95% identical. Same confirming state, same outside-click effect, same handlers. Only the label and a CSS class differed. One variant prop replaces both.</p>

<h3 id="state">State</h3>

<h4 id="the-ref-soup">The ref soup</h4>

<p><strong>Symptom.</strong> The state of one feature gets mutated by handlers of another feature. A cancel button resets three other features’ refs. You touch one feature and you have to check three others.</p>

<p><strong>Cause.</strong> Nothing has walls between features, so every handler has to clean up everyone else’s mess.</p>

<p><strong>Fix.</strong> Run the extraction pass.</p>

<p><strong>Found.</strong> AnnotationInput: five handlers mutated three refs (prevValueRef, historyIndexRef, draftRef) that belonged to one feature. The fix was one hook, not seven. The history feature moved out with its own refs and its own DOM hack. Five call sites became method calls. The file dropped to around 300 lines and the smell was gone. Score went from 5/10 to 7/10. The drag-resize and snippet-trigger features stayed inline. They weren’t tangled.</p>

<h4 id="impossible-states">Impossible states</h4>

<p><strong>Symptom.</strong> Flags allow combinations that can’t happen. isLoading &amp;&amp; isError is a valid value in the type system but a lie in the domain.</p>

<p><strong>Fix.</strong> Union types / discriminated unions. One status field with legal values beats five booleans. If the state has transitions (can’t cancel while saving), use a reducer with explicit transitions, not flags.</p>

<h4 id="derived-state-stored">Derived state stored</h4>

<p><strong>Symptom.</strong> Computed values kept in state, needing sync logic to stay current.</p>

<p><strong>Fix.</strong> Derive during render. useMemo only when the computation is expensive. Sync effects are a bug farm.</p>

<p><strong>Found.</strong> Sidebar computed its groups and counts during render with useMemo. That was already correct. This recipe is the target, not a failure.</p>

<h4 id="write-only-state">Write-only state</h4>

<p><strong>Symptom.</strong> A value is set but never read in that state. You can’t trace any read of it.</p>

<p><strong>Fix.</strong> Delete it. Trace the reads before the writes. If a value is never read before being overwritten, the write is dead code.</p>

<p><strong>Found.</strong> After snippet select, draftRef was set to the new value, but the next read path always overwrites it from the textarea first. The write was unobservable. reset() was equivalent.</p>

<h4 id="set-as-state">Set as state</h4>

<p><strong>Symptom.</strong> A Set in useState. Every update is new Set(prev), mutate, return. Awkward to update immutably.</p>

<p><strong>Fix.</strong> Store as a record keyed by id, or use a reducer with explicit transitions (toggle, clear).</p>

<p><strong>Found.</strong> Sidebar: selectedIds as Set<number>, expandedGroups as Set<string>. Both do the clone-mutate dance.</string></number></p>

<h4 id="two-sources-of-truth">Two sources of truth</h4>

<p><strong>Symptom.</strong> A value prop plus direct DOM writes. The framework controls the value, a handler writes the DOM node directly.</p>

<p><strong>Fix.</strong> One source of truth. Route every write through the state setter. Direct DOM writes fight the framework and break silently the day a value transform or re-render guard appears.</p>

<p><strong>Found.</strong> AnnotationInput’s recallHistory wrote ta.value directly while the textarea was controlled by value={value}.</p>

<h4 id="hidden-globals">Hidden globals</h4>

<p><strong>Symptom.</strong> A module-level mutable singleton. Created once, appended to the document, never removed. Breaks if a second instance ever mounts.</p>

<p><strong>Fix.</strong> Instance-scope it: hold it in a ref, create on demand, remove in an effect cleanup on unmount.</p>

<p><strong>Found.</strong> AnnotationInput’s caretMirror: appended to document.body once, never removed. Worked only because the component never unmounted. It would leak the day it did.</p>

<h3 id="api-and-data-flow">API and data flow</h3>

<h4 id="boolean-prop-explosion">Boolean prop explosion</h4>

<p><strong>Symptom.</strong> isX, isY flags piling up on props or derived internally, driving conditional markup in every branch.</p>

<p><strong>Fix.</strong> Variants or composition instead of flags. When a component has real modes (normal, deleted, broken), make the mode a variant, not three booleans.</p>

<p><strong>Found.</strong> EntryAnnotation: isDeleted, isBroken, expanded, selected all driving the same markup. The deleted and broken action-button groups are near-identical and duplicated across the header and expanded body. Extract the action set, compute it once from the status variant.</p>

<h4 id="prop-drilling">Prop drilling</h4>

<p><strong>Symptom.</strong> Data passed down through layers of components that only forward it.</p>

<p><strong>Fix.</strong> Composition first: pass the rendered element, not the data. Context only when many components at different depths need the same value. Context for everything is a trap.</p>

<h3 id="testing">Testing</h3>

<h4 id="untestable-logic">Untestable logic</h4>

<p><strong>Symptom.</strong> The clever stuff (time bucketing, label formatting, filtering) is trapped inside JSX or handlers. You can’t test it without rendering the whole component.</p>

<p><strong>Fix.</strong> Extract pure function modules. Unit test them directly.</p>

<p><strong>Found.</strong> Sidebar’s recent-groups time bucketing (“Just now”, “Today”, “This week”, “Other”) is pure logic inside the component. It belongs in a module with tests.</p>

<h2 id="5-the-rules---prevention-not-repair">5. The rules - prevention, not repair</h2>

<ul>
  <li>When a component feels all over the place, remove before you add.</li>
  <li>Never put a whole hook object in a dependency array. It’s a new object every render. Destructure the stable callbacks and depend on those.</li>
  <li>Extract at the second or third copy. One copy is fine. Two is the signal. Three is the obligation.</li>
  <li>A hook that wraps one line of state is worse than the inline code.</li>
</ul>

<h2 id="6-glossary---symptom-to-fix">6. Glossary - symptom to fix</h2>

<ul>
  <li>Can’t list what the file does → Triage (the 7 questions)</li>
  <li>Handlers touch each other’s state → The ref soup</li>
  <li>Two nearly-identical files → Duplication</li>
  <li>Too many features in one file → God component</li>
  <li>isX/isY flags everywhere → Boolean prop explosion</li>
  <li>Illegal states are representable → Impossible states</li>
  <li>Data drilled through layers → Prop drilling</li>
  <li>DOM math + logic + JSX in one function → Mixed layers</li>
  <li>Module-level singleton → Hidden globals</li>
  <li>Set but never read → Write-only state</li>
  <li>Computed value stored → Derived state stored</li>
  <li>Logic trapped in JSX → Untestable logic</li>
  <li>Set in state → Set as state</li>
  <li>Controlled + direct DOM writes → Two sources of truth</li>
  <li>Effect re-runs every render → Rules: hook object in deps</li>
  <li>Same effect in many components → Rules: extract at 2nd/3rd copy</li>
  <li>Can’t decide which tool → Techniques: the fix ladder</li>
</ul>]]></content><author><name>Avre Barra</name></author><category term="react" /><category term="maintainability" /><category term="refactoring" /><category term="clean-code" /><category term="cookbook" /><summary type="html"><![CDATA[A cookbook to keep React components maintainable: triage by symptom, extract logic cleanly, and pick the right refactor pattern.]]></summary></entry><entry><title type="html">Goodbye, Makefile</title><link href="https://avrebarra.github.io/goodbye-makefile/" rel="alternate" type="text/html" title="Goodbye, Makefile" /><published>2026-08-04T00:00:00+00:00</published><updated>2026-08-04T00:00:00+00:00</updated><id>https://avrebarra.github.io/goodbye-makefile</id><content type="html" xml:base="https://avrebarra.github.io/goodbye-makefile/"><![CDATA[<p>I replaced my Makefiles with something else this week.</p>

<p>Makefiles, a classic way to run commands in a project. We define targets, and each target has a recipe. With this file, we run commands like <code class="language-plaintext highlighter-rouge">make dev</code> or <code class="language-plaintext highlighter-rouge">make build</code>. Simple and effective way to manage tasks in a project.</p>

<p>Those Makefiles are fine. But I think I found a way that fits me better.</p>

<h2 id="why-i-switched">Why I Switched</h2>

<p>I used Makefiles for years, like everyone else. They do what they’re for. But what they’re for is narrow: a list of targets, each with a recipe, and every recipe is a shell fragment with invisible rules around it. Tabs matter. Quoting gets weird. The moment a command needs real logic, a condition or an env check, you’re doing shell gymnastics inside a DSL that keeps getting in the way.</p>

<p>Now that AI is here, the last reason to stay is gone. Writing a script used to cost setup time; now my agent writes it for me. All that remains is the tool itself, and Makefile’s feature set is too restrictive to justify keeping it.</p>

<p>What I actually want from a task runner is simple. I want to type a short command and have the right thing happen. I want to read the file later and see every command it offers at a glance. And I want scripting flexibility when one of my commands needs real logic.</p>

<p>I call mine <strong>runtask</strong>. By runtask I mean a single executable Ruby file at the root of the repo. No gems, no dependencies, nothing to learn. Commands are entries in a hash, arguments get matched and dispatched, and anything complicated is just Ruby.</p>

<h2 id="the-skill-that-writes-it-for-me">The Skill That Writes It for Me</h2>

<p>The part that changed how I build is not the file itself. It’s that I stopped writing it by hand.</p>

<p>My coding agent loads skills, small instruction files that package a pattern. I made one for this pattern, called runtask-maker. Now when I start a project I say “set up my runtask” and the agent produces the same file, in the same shape, every time. The skill is short. It holds the rules and a template:</p>

<ul>
  <li>always <code class="language-plaintext highlighter-rouge">chmod +x</code> the file</li>
  <li>stdlib Ruby only, no gems</li>
  <li>every command must appear in the usage text</li>
  <li>use <code class="language-plaintext highlighter-rouge">bail</code> and <code class="language-plaintext highlighter-rouge">run</code> helpers, never a raw <code class="language-plaintext highlighter-rouge">system()</code> call</li>
</ul>

<p>The chmod one matters most. It’s the difference between <code class="language-plaintext highlighter-rouge">./runtask</code> working and a cold “command not found”.</p>

<h2 id="the-file">The File</h2>

<p>Every runtask file has four blocks: config, helpers, commands, dispatch. Config holds the script directory and constants. Helpers load <code class="language-plaintext highlighter-rouge">.env</code> files and run commands with loud errors. Commands is a hash of argument arrays to lambdas. Dispatch matches your input and prints usage when nothing matches.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">COMMANDS</span> <span class="o">=</span> <span class="p">{</span>
  <span class="sx">%w[core dev]</span>  <span class="o">=&gt;</span> <span class="o">-&gt;</span> <span class="p">{</span> <span class="n">run</span> <span class="s2">"cd core &amp;&amp; gow run ."</span> <span class="p">},</span>
  <span class="sx">%w[web build]</span> <span class="o">=&gt;</span> <span class="o">-&gt;</span> <span class="p">{</span> <span class="n">run</span> <span class="s2">"cd web &amp;&amp; npm run build"</span> <span class="p">},</span>
  <span class="sx">%w[deploy]</span>    <span class="o">=&gt;</span> <span class="o">-&gt;</span> <span class="p">{</span>
    <span class="n">bail</span><span class="p">(</span><span class="s2">"VM_USER missing, add it to .env"</span><span class="p">)</span> <span class="k">if</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"VM_USER"</span><span class="p">].</span><span class="nf">to_s</span><span class="p">.</span><span class="nf">empty?</span>
    <span class="n">run</span> <span class="s2">"ssh </span><span class="si">#{</span><span class="no">ENV</span><span class="p">[</span><span class="s2">"VM_USER"</span><span class="p">]</span><span class="si">}</span><span class="s2">@vm 'docker compose up -d'"</span>
  <span class="p">},</span>
<span class="p">}.</span><span class="nf">freeze</span>
</code></pre></div></div>

<p>That’s the whole surface. No phony targets, no variable plumbing, no tab rules. Two of those commands are from real projects. One of them stops before touching the server if the env is wrong. In a Makefile, that same guard is a shell script sitting inside a target.</p>

<h2 id="using-it">Using It</h2>

<p>The part I like most: run the file with no arguments and it tells you everything.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./runtask
Usage: ./runtask &lt;command&gt;

Commands:
  core dev    Start Go API with hot reload
  web build   Build frontend for production
  deploy      Sync and restart services on the VM
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">./runtask core dev</code> runs the command. Adding a new one is a line in the hash and a line in the usage block. That’s the whole maintenance story.</p>

<h2 id="additional-making-it-feel-native">Additional: Making It Feel Native</h2>

<p><code class="language-plaintext highlighter-rouge">./runtask</code> works, but the dots bugged me. <code class="language-plaintext highlighter-rouge">make</code> never makes you type <code class="language-plaintext highlighter-rouge">./make</code>. So I added a thin wrapper to my PATH - a small shell script that just runs <code class="language-plaintext highlighter-rouge">./runtask</code> in the current directory.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>runtask deploy    <span class="c"># same as ./runtask deploy, no dots</span>
</code></pre></div></div>

<p>And because the command list lives in the file, completion can read it straight out of there:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ runtask &lt;TAB&gt;
backup   deploy   pull
</code></pre></div></div>

<p>Add a command to the hash, and TAB completion picks it up on the next press. No separate completion file to keep in sync. The wrapper sets it all up for you:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-fsSL</span> https://avrebarra.github.io/assets/runtask <span class="nt">-o</span> ~/bin/runtask <span class="o">&amp;&amp;</span> <span class="nb">chmod</span> +x ~/bin/runtask
runtask <span class="nt">--setup</span>       <span class="c"># installs completion for your shell (zsh, bash, fish)</span>
runtask <span class="nt">--uninstall</span>   <span class="c"># removes everything, no trace</span>
</code></pre></div></div>

<p>It writes the right completion file per shell - compinit for zsh, a marked source block in <code class="language-plaintext highlighter-rouge">.bashrc</code> for bash, a file in the completions dir for fish. Everything reversible with one command.</p>

<h2 id="closing-thoughts">Closing Thoughts</h2>

<p>Thats how I replaced my Makefiles with a Ruby script file this week. Makefile is fine. But I think I found out that we are entitled to a better way going forward.</p>

<p>This is just one more stuff that made me more convinced that nowadays with AI, there’s more reason to ditch restrictive stuffs as we can get the better stuffs in a practically easier, but still more flexible, readable, and maintainable way.</p>

<p>This one usecase of runtask pattern is simple, but it works better. Guess what comes next?</p>

<h2 id="appendix">Appendix</h2>

<p><strong>The skill my agent loads: <code class="language-plaintext highlighter-rouge">runtask-maker/SKILL.md</code></strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---
name: runtask-maker
description: &gt;
  Create or extend the "runtask" file - a single executable Ruby script at the repo
  root that replaces a Makefile with a typed command table (`./runtask core dev`).
  Use when user says "set up my runtask", "make a task runner", "add a command to
  runtask", "replace the makefile with a script", "create my scripts file", "add a
  deploy/dev command script", or when a project needs a Makefile and a script would
  be cleaner. Backs the runtask with a copy-paste template and always sets chmod +x.
---

## runtask - the Ruby task-runner file

One executable Ruby file at repo root. Replaces Makefile. Runs arbitrary shell
commands with typed args: `./runtask core dev`, `./runtask deploy`.

Runs anywhere (macOS/Linux, no gems, stdlib only). Better than Makefile: real
language for conditionals/env checks, readable errors, no tab-sensitivity.

## When to use

- Multi-command project (dev/test/build/deploy/lint across domains) - use runtask.
- Monorepo with multiple apps → namespace commands: `%w[core dev]`, `%w[web release]`.
- Deploy/ops workflows with env checks (SSH, scp, docker compose) - use runtask.

**When NOT to use (YAGNI):**
- Single command → just run it, no file.
- Single-language simple app with package.json → use its built-in `scripts` block.
- User explicitly asks for a Makefile - don't override.

## Non-negotiable rules

1. **ALWAYS `chmod +x runtask`** after creating or copying the file. Verify with `ls -l`.
2. File name: `runtask`, no extension, repo root. (Existing projects may call it
   `scripts` - same pattern, leave their name unless user asks to rename.)
3. Ruby only, stdlib only. No gems, no bundler.
4. Run via `./runtask &lt;cmd...&gt;` - never `ruby runtask`. Usage text must say `./runtask`.
5. Every command must be in the USAGE heredoc - file self-documents.
6. Exit codes: 0 success, 1 failure or unknown command.
7. `bail` + `run` helpers always included (see template). Never raw `system()` without error handling.

## Structure (follow exactly)

1. `#!/usr/bin/env ruby` + `frozen_string_literal: true`
2. `# ─── config ───` - `SCRIPT_DIR = File.dirname(File.expand_path(__FILE__))`, constants
3. Helpers: `load_env(path)` (parses `.env`-style files), `bail(msg)`, `run(cmd)`
4. `# ─── commands ───` - `COMMANDS` hash: array of downcase args → lambda
5. `# ─── dispatch ───` - `ARGV.map(&amp;:downcase)` → lookup → `exit cmd.call ? 0 : 1`
   or print USAGE heredoc + exit 1

Section comments lowercase, one line, no numbering (project convention).

## Template

Full skeleton: `template/runtask` in this skill dir. Copy it, edit COMMANDS +
USAGE, `chmod +x`. Command bodies use `run "..."` (prints + bails on failure)
or lambdas with `bail(...)` for preconditions (env vars, files, SSH reachability).

## Conventions

- Commands lowercase. Namespaced pairs for monorepo: `%w[core dev]`, `%w[web release]`.
- Secrets/env → `.env` at repo root, loaded by `load_env`. Never hardcode.
- `.env` stays gitignored. `load_env` silently skips missing file - no crash.
- Multi-step command → `run` each step, print `&gt;&gt;&gt;` progress lines, end with status.
- Long helper output (e.g. cron hints) → print as `#`-prefixed comment block.

## Example

```ruby
COMMANDS = {
  %w[dev]      =&gt; -&gt; { run "npm run dev" },
  %w[deploy]   =&gt; -&gt; {
    bail("VM_USER not set - put it in .env") if ENV["VM_USER"].to_s.empty?
    run "ssh #{ENV["VM_USER"]}@vm 'cd app &amp;&amp; docker compose up -d'"
  },
}.freeze
```
</code></pre></div></div>

<p><strong>The template it generates: <code class="language-plaintext highlighter-rouge">template/runtask</code></strong></p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env ruby</span>
<span class="c1"># frozen_string_literal: true</span>

<span class="c1"># ─── config ──────────────────────────────────────────────────────────────────</span>

<span class="no">SCRIPT_DIR</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">dirname</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">expand_path</span><span class="p">(</span><span class="kp">__FILE__</span><span class="p">))</span>

<span class="k">def</span> <span class="nf">load_env</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
  <span class="k">return</span> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">exist?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>

  <span class="no">File</span><span class="p">.</span><span class="nf">readlines</span><span class="p">(</span><span class="n">path</span><span class="p">).</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span>
    <span class="n">line</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="nf">strip</span>
    <span class="k">next</span> <span class="k">if</span> <span class="n">line</span><span class="p">.</span><span class="nf">empty?</span> <span class="o">||</span> <span class="n">line</span><span class="p">.</span><span class="nf">start_with?</span><span class="p">(</span><span class="s2">"#"</span><span class="p">)</span>

    <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="s2">"="</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
    <span class="no">ENV</span><span class="p">[</span><span class="n">key</span><span class="p">.</span><span class="nf">strip</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span><span class="p">.</span><span class="nf">strip</span><span class="p">.</span><span class="nf">delete</span><span class="p">(</span><span class="sx">%q('")</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">bail</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
  <span class="nb">warn</span> <span class="s2">"❌ </span><span class="si">#{</span><span class="n">msg</span><span class="si">}</span><span class="s2">"</span>
  <span class="nb">exit</span> <span class="mi">1</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">cmd</span><span class="p">)</span>
  <span class="nb">puts</span> <span class="s2">"  $ </span><span class="si">#{</span><span class="n">cmd</span><span class="si">}</span><span class="s2">"</span>
  <span class="n">bail</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">cmd</span><span class="p">.</span><span class="nf">split</span><span class="p">.</span><span class="nf">first</span><span class="si">}</span><span class="s2"> failed"</span><span class="p">)</span> <span class="k">unless</span> <span class="nb">system</span><span class="p">(</span><span class="n">cmd</span><span class="p">)</span>
<span class="k">end</span>

<span class="n">load_env</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="no">SCRIPT_DIR</span><span class="si">}</span><span class="s2">/.env"</span><span class="p">)</span>

<span class="c1"># ─── commands ────────────────────────────────────────────────────────────────</span>

<span class="no">COMMANDS</span> <span class="o">=</span> <span class="p">{</span>
  <span class="c1"># %w[dev] =&gt; -&gt; { run "npm run dev" },</span>
  <span class="c1"># %w[test] =&gt; -&gt; { run "npm test" },</span>
  <span class="c1"># %w[core dev] =&gt; -&gt; { run "cd core &amp;&amp; gow run ." },</span>
<span class="p">}.</span><span class="nf">freeze</span>

<span class="c1"># ─── dispatch ────────────────────────────────────────────────────────────────</span>

<span class="n">input</span> <span class="o">=</span> <span class="no">ARGV</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:downcase</span><span class="p">)</span>
<span class="n">cmd</span>   <span class="o">=</span> <span class="no">COMMANDS</span><span class="p">[</span><span class="n">input</span><span class="p">]</span>

<span class="k">if</span> <span class="n">cmd</span>
  <span class="nb">exit</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">call</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="mi">1</span>
<span class="k">else</span>
  <span class="nb">puts</span> <span class="o">&lt;&lt;~</span><span class="no">USAGE</span><span class="sh">
    Usage: ./runtask &lt;command&gt;

    Commands:
      dev     Start dev server with HMR
      test    Run tests
</span><span class="no">  USAGE</span>
  <span class="nb">exit</span> <span class="mi">1</span>
<span class="k">end</span>
</code></pre></div></div>

<p><strong>The wrapper that makes it feel native: <code class="language-plaintext highlighter-rouge">assets/runtask</code></strong></p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="c"># runtask - bare `runtask &lt;cmd&gt;` runner + one-command completion setup</span>
<span class="c"># install:    curl -fsSL https://avrebarra.github.io/assets/runtask -o ~/bin/runtask &amp;&amp; chmod +x ~/bin/runtask</span>
<span class="c"># setup:      runtask --setup [zsh|bash|fish]</span>
<span class="c"># uninstall:  runtask --uninstall</span>
<span class="nb">set</span> <span class="nt">-u</span>

say<span class="o">()</span> <span class="o">{</span> <span class="nb">printf</span> <span class="s1">'%s\n'</span> <span class="s2">"</span><span class="nv">$*</span><span class="s2">"</span><span class="p">;</span> <span class="o">}</span>

<span class="c"># ─── command parsing ─────────────────────────────────────────────────────────</span>

runtask_cmds<span class="o">()</span> <span class="o">{</span>
  <span class="nb">grep</span> <span class="nt">-o</span> <span class="s1">'%w\[[^]]*\]'</span> ./runtask 2&gt;/dev/null <span class="se">\</span>
    | <span class="nb">sed</span> <span class="s1">'s/%w\[//; s/\]//'</span> <span class="se">\</span>
    | <span class="nb">awk</span> <span class="s1">'{print $1}'</span> <span class="se">\</span>
    | <span class="nb">sort</span> <span class="nt">-u</span>
<span class="o">}</span>

<span class="c"># ─── completion bodies ───────────────────────────────────────────────────────</span>

zsh_body<span class="o">()</span> <span class="o">{</span>
  <span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
#compdef runtask ./runtask

# complete runtask commands by parsing %w[...] definitions from ./runtask
_runtask() {
  local -a cmds
  if [[ -r ./runtask ]]; then
    cmds=(</span><span class="k">${</span><span class="p">(f)</span><span class="s2">"</span><span class="si">$(</span><span class="nb">grep</span> <span class="nt">-o</span> <span class="s1">'%w\[[^]]*\]'</span> ./runtask | <span class="nb">sed</span> <span class="s1">'s/%w\[//; s/\]//'</span> | <span class="nb">awk</span> <span class="s1">'{print $1}'</span> | <span class="nb">sort</span> <span class="nt">-u</span><span class="si">)</span><span class="s2">"</span><span class="k">}</span><span class="sh">)
  fi
  compadd -- </span><span class="nv">$cmds</span><span class="sh">
}

_runtask "</span><span class="nv">$@</span><span class="sh">"
</span><span class="no">EOF
</span><span class="o">}</span>

bash_body<span class="o">()</span> <span class="o">{</span>
  <span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
# bash completion for runtask - parse %w[...] commands from ./runtask
_runtask() {
  local cur cmds
  cur="</span><span class="k">${</span><span class="nv">COMP_WORDS</span><span class="p">[COMP_CWORD]</span><span class="k">}</span><span class="sh">"
  cmds="</span><span class="si">$(</span><span class="nb">grep</span> <span class="nt">-o</span> <span class="s1">'%w\[[^]]*\]'</span> ./runtask 2&gt;/dev/null | <span class="nb">sed</span> <span class="s1">'s/%w\[//; s/\]//'</span> | <span class="nb">awk</span> <span class="s1">'{print $1}'</span> | <span class="nb">sort</span> <span class="nt">-u</span><span class="si">)</span><span class="sh">"
  COMPREPLY=( </span><span class="si">$(</span><span class="nb">compgen</span> <span class="nt">-W</span> <span class="s2">"</span><span class="nv">$cmds</span><span class="s2">"</span> <span class="nt">--</span> <span class="s2">"</span><span class="nv">$cur</span><span class="s2">"</span><span class="si">)</span><span class="sh"> )
}
complete -F _runtask runtask
</span><span class="no">EOF
</span><span class="o">}</span>

fish_body<span class="o">()</span> <span class="o">{</span>
  <span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
# fish completion for runtask - parse %w[...] commands from ./runtask
complete -c runtask -xa '(grep -o "%w</span><span class="se">\[</span><span class="sh">[^]]*</span><span class="se">\]</span><span class="sh">" ./runtask 2&gt;/dev/null | sed "s/%w</span><span class="se">\[</span><span class="sh">//; s/</span><span class="se">\]</span><span class="sh">//" | awk "{print </span><span class="se">\$</span><span class="sh">1}" | sort -u)'
</span><span class="no">EOF
</span><span class="o">}</span>

<span class="c"># ─── install helpers ─────────────────────────────────────────────────────────</span>

add_marker<span class="o">()</span> <span class="o">{</span>  <span class="c"># add_marker &lt;file&gt; &lt;block-file&gt;</span>
  <span class="nv">file</span><span class="o">=</span><span class="nv">$1</span>
  <span class="nv">block</span><span class="o">=</span><span class="nv">$2</span>
  strip_marker <span class="s2">"</span><span class="nv">$file</span><span class="s2">"</span>
  <span class="nb">printf</span> <span class="s1">'\n# runtask:start\n'</span> <span class="o">&gt;&gt;</span><span class="s2">"</span><span class="nv">$file</span><span class="s2">"</span>
  <span class="nb">cat</span> <span class="s2">"</span><span class="nv">$block</span><span class="s2">"</span> <span class="o">&gt;&gt;</span><span class="s2">"</span><span class="nv">$file</span><span class="s2">"</span>
  <span class="nb">printf</span> <span class="s1">'# runtask:end\n'</span> <span class="o">&gt;&gt;</span><span class="s2">"</span><span class="nv">$file</span><span class="s2">"</span>
<span class="o">}</span>

strip_marker<span class="o">()</span> <span class="o">{</span>
  <span class="nv">file</span><span class="o">=</span><span class="nv">$1</span>
  <span class="o">[</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$file</span><span class="s2">"</span> <span class="o">]</span> <span class="o">||</span> <span class="k">return </span>0
  <span class="nb">awk</span> <span class="s1">'/^# runtask:start$/{skip=1} !skip{print} /^# runtask:end$/{skip=0}'</span> <span class="s2">"</span><span class="nv">$file</span><span class="s2">"</span> <span class="o">&gt;</span><span class="s2">"</span><span class="nv">$file</span><span class="s2">.tmp"</span> <span class="o">||</span> <span class="nb">true
  mv</span> <span class="s2">"</span><span class="nv">$file</span><span class="s2">.tmp"</span> <span class="s2">"</span><span class="nv">$file</span><span class="s2">"</span>
<span class="o">}</span>

install_zsh<span class="o">()</span> <span class="o">{</span>
  <span class="nv">zdir</span><span class="o">=</span><span class="k">${</span><span class="nv">ZSH_CUSTOM</span><span class="k">:-</span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.oh-my-zsh/custom"</span><span class="k">}</span>
  <span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$zdir</span><span class="s2">/completions"</span>
  zsh_body <span class="o">&gt;</span><span class="s2">"</span><span class="nv">$zdir</span><span class="s2">/completions/_runtask"</span>
  say <span class="s2">"zsh: wrote </span><span class="nv">$zdir</span><span class="s2">/completions/_runtask (auto-loaded by compinit)"</span>
<span class="o">}</span>

install_bash<span class="o">()</span> <span class="o">{</span>
  <span class="nv">bdir</span><span class="o">=</span><span class="nv">$HOME</span>/.bash_completion.d
  <span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$bdir</span><span class="s2">"</span>
  bash_body <span class="o">&gt;</span><span class="s2">"</span><span class="nv">$bdir</span><span class="s2">/runtask"</span>
  <span class="nv">rc</span><span class="o">=</span><span class="nv">$HOME</span>/.bashrc
  <span class="nv">block</span><span class="o">=</span><span class="si">$(</span><span class="nb">mktemp</span><span class="si">)</span>
  <span class="nb">printf</span> <span class="s1">'[ -d "$HOME/.bash_completion.d" ] &amp;&amp; for f in "$HOME"/.bash_completion.d/*; do [ -r "$f" ] &amp;&amp; . "$f"; done\n'</span> <span class="o">&gt;</span><span class="s2">"</span><span class="nv">$block</span><span class="s2">"</span>
  add_marker <span class="s2">"</span><span class="nv">$rc</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$block</span><span class="s2">"</span>
  <span class="nb">rm</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$block</span><span class="s2">"</span>
  say <span class="s2">"bash: wrote </span><span class="nv">$bdir</span><span class="s2">/runtask + source block in </span><span class="nv">$rc</span><span class="s2">"</span>
<span class="o">}</span>

install_fish<span class="o">()</span> <span class="o">{</span>
  <span class="nv">fdir</span><span class="o">=</span><span class="nv">$HOME</span>/.config/fish/completions
  <span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$fdir</span><span class="s2">"</span>
  fish_body <span class="o">&gt;</span><span class="s2">"</span><span class="nv">$fdir</span><span class="s2">/runtask.fish"</span>
  say <span class="s2">"fish: wrote </span><span class="nv">$fdir</span><span class="s2">/runtask.fish (auto-loaded)"</span>
<span class="o">}</span>

<span class="c"># ─── setup / uninstall ───────────────────────────────────────────────────────</span>

setup<span class="o">()</span> <span class="o">{</span>
  <span class="nv">shell</span><span class="o">=</span><span class="k">${</span><span class="nv">1</span><span class="k">:-}</span>
  <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$shell</span><span class="s2">"</span> <span class="o">]</span> <span class="o">||</span> <span class="nv">shell</span><span class="o">=</span><span class="k">${</span><span class="nv">SHELL</span><span class="p">##*/</span><span class="k">}</span>
  <span class="k">case</span> <span class="s2">"</span><span class="nv">$shell</span><span class="s2">"</span> <span class="k">in
    </span>zsh<span class="p">)</span>  install_zsh <span class="p">;;</span>
    bash<span class="p">)</span> install_bash <span class="p">;;</span>
    fish<span class="p">)</span> install_fish <span class="p">;;</span>
    <span class="k">*</span><span class="p">)</span>    say <span class="s2">"runtask: unsupported shell '</span><span class="nv">$shell</span><span class="s2">' (zsh|bash|fish)"</span> <span class="o">&gt;</span>&amp;2<span class="p">;</span> <span class="nb">exit </span>1 <span class="p">;;</span>
  <span class="k">esac</span>
<span class="o">}</span>

uninstall<span class="o">()</span> <span class="o">{</span>
  <span class="nb">rm</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.oh-my-zsh/custom/completions/_runtask"</span> 2&gt;/dev/null
  <span class="nb">rm</span> <span class="nt">-rf</span> <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.bash_completion.d"</span> 2&gt;/dev/null
  <span class="nb">rm</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.config/fish/completions/runtask.fish"</span> 2&gt;/dev/null
  strip_marker <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.bashrc"</span>
  say <span class="s2">"runtask: removed completion files + rc markers"</span>
<span class="o">}</span>

<span class="c"># ─── dispatch ────────────────────────────────────────────────────────────────</span>

<span class="k">case</span> <span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="k">:-}</span><span class="s2">"</span> <span class="k">in</span>
  <span class="nt">--setup</span><span class="p">)</span>  setup <span class="s2">"</span><span class="k">${</span><span class="nv">2</span><span class="k">:-}</span><span class="s2">"</span> <span class="p">;;</span>
  <span class="nt">--uninstall</span><span class="p">)</span> uninstall <span class="p">;;</span>
  <span class="nt">--help</span><span class="p">|</span><span class="nt">-h</span><span class="p">)</span>
    say <span class="s2">"runtask - bare </span><span class="se">\`</span><span class="s2">runtask &lt;cmd&gt;</span><span class="se">\`</span><span class="s2"> runner with one-command completion setup"</span>
    say <span class="s2">"  runtask &lt;cmd...&gt;        run ./runtask in current dir"</span>
    say <span class="s2">"  runtask --setup [shell] install completion (zsh|bash|fish)"</span>
    say <span class="s2">"  runtask --uninstall     remove completion + rc markers"</span>
    <span class="nb">exit </span>0 <span class="p">;;</span>
  <span class="k">*</span><span class="p">)</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-r</span> ./runtask <span class="o">]</span><span class="p">;</span> <span class="k">then
      </span><span class="nb">exec</span> ./runtask <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
    <span class="k">else
      </span>say <span class="s2">"runtask: no ./runtask here"</span> <span class="o">&gt;</span>&amp;2
      <span class="nb">exit </span>1
    <span class="k">fi</span>
    <span class="p">;;</span>
<span class="k">esac</span>
</code></pre></div></div>]]></content><author><name>Avre Barra</name></author><category term="tooling" /><category term="ruby" /><summary type="html"><![CDATA[Replacing Makefile with a single Ruby runtask file - less shell gymnastics, better scripting, and a reusable agent skill for any repo.]]></summary></entry><entry><title type="html">AI Generation. Governance Has to Keep Up.</title><link href="https://avrebarra.github.io/ai-generation-governance-has-to-keep-up/" rel="alternate" type="text/html" title="AI Generation. Governance Has to Keep Up." /><published>2026-05-19T00:00:00+00:00</published><updated>2026-05-19T00:00:00+00:00</updated><id>https://avrebarra.github.io/ai-generation-governance-has-to-keep-up</id><content type="html" xml:base="https://avrebarra.github.io/ai-generation-governance-has-to-keep-up/"><![CDATA[<p><em>“AI removes the bottleneck.” “You can move ten times faster.” “Anyone who doesn’t use AI will be replaced by someone who does.”</em></p>

<p>I’m skeptical of how that gets interpreted.</p>

<p>AI does generate fast, that part is not in dispute. Across writing, code, research, and imagery, the pace advantage is real. Content that took hours takes minutes. Code drafted in an hour. Research summaries, articles, analyses, volume is no longer the constraint.</p>

<p>But speed of generation is not the same as capacity for governance.</p>

<p>As AI tools absorb more of the production work, the human role shifts toward reviewing, validating, and curating, not creating. That sounds like progress. It isn’t, not automatically. The shift created a new accountability, one most have not yet picked up.</p>

<p>The bottleneck has moved from production to comprehension.</p>

<h2 id="the-generation-governance-gap">The generation-governance gap</h2>

<p>The pressure AI creates runs almost entirely in one direction: produce more, faster. Articles accumulate. Codebases grow. Research outputs multiply. Images proliferate. What doesn’t accelerate is the work of reviewing, shaping, and pruning what no longer belongs, or never should have been produced in the first place.</p>

<p>AI-generated output, across domains, tends to be:</p>

<ul>
  <li>More voluminous than necessary, optimizing for output over substance</li>
  <li>More redundant, with missed opportunities for consolidation, leading to noise and drift</li>
  <li>Weighted toward surface coherence over depth, accuracy, or fit</li>
  <li>Prone to hallucinations, reasoning gaps, and mismatches with actual context</li>
</ul>

<p>None of this makes AI tools less useful. It makes human oversight more load-bearing than ever before.</p>

<p>We once produced everything we later reviewed. That symmetry is gone. AI can generate in an hour what takes days to fully evaluate. That gap opened while people were celebrating the speed. Most are still not looking at it.</p>

<h2 id="the-governance-floor">The governance floor</h2>

<p>Quality is the minimum condition for human oversight to function. In code: readability, structure, modularity. In writing: accuracy, coherence, original voice. In research: rigor, citation integrity, sound reasoning. The properties that make a body of work navigable at speed, whatever the domain.</p>

<p>When these properties hold, anyone can move through a body of work quickly enough to actually govern it. They can identify where things don’t fit. They can see what is accumulating. They can make the call to prune. The human stays responsible.</p>

<p>When they break down, none of that works at the pace AI demands. If the output cannot be reasoned about, the human in the loop becomes decorative.</p>

<p>Maintaining quality reduces cognitive effort, enhances transparency, and lowers the cost of every intervention that follows:</p>

<ul>
  <li>Issues surface earlier, before they compound</li>
  <li>Corrections become targeted rather than exploratory</li>
  <li>Misalignments are visible before they calcify</li>
</ul>

<p>These are not quality metrics. They are the mechanisms by which we retain meaningful control over a body of work that now grows faster than any one person can produce. And the same AI capability accelerating generation can be turned toward this too, catching drift, flagging redundancy, surfacing what no longer belongs. The leverage works in both directions.</p>

<h2 id="what-this-means-for-us">What this means for us</h2>

<p>When AI makes generation this fast, it is easy for effort to concentrate entirely there. Generation is visible, rewarding, immediate. Oversight is slower and harder to justify. The risk is that the imbalance grows quietly, until the output becomes too large to govern.</p>

<p>Maintaining quality is no longer primarily about the next person who picks up where we left off. It is about whether we can still see what is happening inside a system, or a body of work, that AI is actively accelerating.</p>

<p>Maintaining, shaping, pruning: these become even more primary tasks.</p>

<p>The practical path is not to resist the speed. It is to redirect some of that same capability toward governance. Generate fast. But hold the responsibility. Use AI on both sides, production and oversight, so the pace is sustainable, not just impressive.</p>

<p>The gap between what AI can produce and what we can still govern is where quality is decided now.</p>]]></content><author><name>Avre Barra</name></author><summary type="html"><![CDATA[AI generation is fast, governance must keep up - why AI code and content need human oversight and practical guardrails.]]></summary></entry><entry><title type="html">Everyone Wins When Workload Drops</title><link href="https://avrebarra.github.io/everyone-wins-when-developer-workload-drops/" rel="alternate" type="text/html" title="Everyone Wins When Workload Drops" /><published>2025-09-04T01:40:46+00:00</published><updated>2025-09-04T01:40:46+00:00</updated><id>https://avrebarra.github.io/everyone-wins-when-developer-workload-drops</id><content type="html" xml:base="https://avrebarra.github.io/everyone-wins-when-developer-workload-drops/"><![CDATA[<p>Back in my days as squad lead at LinkAja, I made a decision that went against the common ways.</p>

<p>In the golden days of microservices hypes, while most of us were hyping up toward microservices, I decided to actively refactor back into a monolithic architectures. I was technically questionied at first of this but I go on. Then things changed: we went from a team working over the nights to a team with zero works past 4PM for two years. We deliver all business-requests fast. We donned strategic initiatives for fun. We even started helping up other teams’ backlogs.</p>

<p>From that one decision, I learned that: <strong>dropping teams workload should always be core targets.</strong></p>

<h2 id="everyone-wins-when-we-cut-workload">Everyone Wins When We Cut Workload</h2>

<p>Let’s go back to the regressive-sounding decision I made: my team was a small 4-person squad responsible for governmental projects. We frequently faced sudden development requests with urgent deadlines. People were asking us to create an E2E mobile app from scratch integrating with 4 national banks within &lt;20 working days (this actually happened; <em>and please mind Indonesian banks are bureautically slow</em>). You can imagine the pressure we were giving to ourselves spinning up multiple services every time a project came up. That’s why handling lots of microservices was not for us. Switching to a monolith was to make things simpler, help us deliver faster. The strategy worked wonders in the long run with our next big government projects.</p>

<p>But let me be clear: this isn’t about advocating for monoliths over microservices. Not about being rad and going against the current. The core principle is about <strong>leaning things up</strong>, removing current states that doesn’t serve your team’s specific context and constraints, and in the process, reducing engineers workload.</p>

<p>Whether that means consolidating microservices into a monolith, simplifying deployment pipelines, reducing the number of tools in your stack, or streamlining processes, the goal is the same: eliminate overheads that prevents your team from doing things agile-ly. <em>(TODO: write about this)</em> I argue that simplifying work processes shouldn’t be something that requires extensive justification. <strong>Leaning up processes should be as northstar as gaining more revenue</strong>. It should not require sophisticated business justification. It’s a just common sense.</p>

<h2 id="the-benefits-for-manpowers">The Benefits for Manpowers</h2>

<p>You might think that the direction I’m going with this post is about work-life balance. While that’s part of it, the real benefits of reducing manpowers workload go far beyond just avoiding manpower burnout.</p>

<p>When manpowers aren’t constantly firefighting or context-switching between fragmented systems, they unlock cognitive space for meaningful work. This isn’t just about work-life balance, it’s about creating conditions where people can actually excel at what they do.</p>

<h3 id="1-it-fosters-technical-growth-and-innovation">1. It Fosters Technical Growth and Innovation</h3>

<p>Sustainable workloads create space for continuous learning. Manpowers can stay current with technologies, explore new patterns, and make informed architectural decisions. This isn’t just professional development, it’s competitive advantage through better technology choices.</p>

<h3 id="2-it-enhances-agility-and-maneuverability">2. It Enhances Agility and Maneuverability</h3>

<p>Manpowers with capacity remain agile when priorities shift or emergencies arise. An underutilized manpowers isn’t wasted, they’re prepared for opportunity. At LinkAja, this agility meant handling urgent government pilots while maintaining core product quality.</p>

<h3 id="3-it-enables-sustainable-performance">3. It Enables Sustainable Performance</h3>

<p>Optimized workloads enable consistent high performance over months and years rather than unsustainable sprints. When people feel sustainable in their work, they bring full creative capabilities to problems, writing code that works elegantly and extensibly.</p>

<h2 id="the-benefits-for-customers">The Benefits for Customers</h2>

<p>Customers don’t care about development processes, they care about product quality and reliability. But overworked manpowers create technical debt, introduce bugs, and make rushed decisions that compound over time, directly impacting user experience.</p>

<p>When development teams operate with optimized workloads, the benefits flow directly to end users in measurable ways:</p>

<h3 id="1-it-increases-product-reliability">1. It Increases Product Reliability</h3>

<p>Manpowers with adequate cognitive bandwidth write more defensive code, consider edge cases, and implement proper error handling. They have time to write comprehensive tests and conduct thorough code reviews. This translates to fewer production outages, reduced downtime, and more stable user experiences.</p>

<p>Teams operating at sustainable capacity also can respond to critical issues immediately without derailing existing work. When manpowers aren’t already at 100% utilization, they have the mental clarity to diagnose problems quickly and implement proper fixes rather than temporary patches that create future issues.</p>

<h3 id="2-it-improves-feature-quality">2. It Improves Feature Quality</h3>

<p>Features built by well-rested manpowers are more intuitive, performant, and user-friendly. Manpowers have time to consider the user journey, optimize performance, and polish interactions. They can iterate on designs based on user feedback rather than shipping the first working version due to time pressure.</p>

<h3 id="3-responsive-customer-support">3. Responsive Customer Support</h3>

<p>When development teams aren’t firefighting constantly, they can allocate resources to address customer feedback quickly. Feature requests get proper evaluation, bugs get prioritized appropriately, and improvements ship on predictable schedules.</p>

<p>The result? More reliable products, faster feature delivery, and better user experiences. Customers benefit from the downstream effects of sustainable development practices, even when they’re unaware of the engineering processes that enable them. They simply experience products that work consistently, features that make sense, and support teams that respond quickly to their needs.</p>

<h2 id="the-benefits-for-business">The Benefits for Business</h2>

<p>Some might raise up a question “Things are already going fine. What we are freeing capacity for? Aren’t we just paying people to do less?”</p>

<p>I would say that is a very misconception. Leaning the process just to free up workload is not wasting money. We always need to have free capacity. Capacity is not waste. Capacity is a strategic asset. To do things. To plan things. To actually figure out what to do if we are having free capacity.</p>

<p>Optimizing manpowers workloads isn’t just a “nice to have”, it’s a strategic business decision that drives measurable financial benefits. While it may seem counterintuitive to reduce what we are making the manpowers do, the reality is that this is actually a practice that will win in the long run.</p>

<h3 id="1-it-eliminates-technical-debt-interest-payments">1. It Eliminates Technical Debt Interest Payments</h3>

<p>Rushed stuffs created from a bad working environment generates maintenance overhead that compounds like financial interest. It especially come back faster in a fast iterating business like software businesses. Every shortcut taken under pressure becomes a future tax on development velocity. Technical debt doesn’t just slow down feature delivery, it creates hidden operational costs through increased debugging time, more production incidents, and expensive refactoring cycles.</p>

<p>Teams with sustainable workloads write cleaner code initially, reducing long-term maintenance costs by 30-50%. This isn’t perfectionism, it’s fiscal responsibility.</p>

<h3 id="2-it-enables-strategic-business-engagement">2. It Enables Strategic Business Engagement</h3>

<p>When manpowers aren’t overwhelmed with firefighting, they can engage meaningfully with business objectives. They understand customer needs, contribute to product strategy, and make technical decisions aligned with business goals.</p>

<p>This deeper involvement creates better products because engineers who understand the “why” build solutions that truly serve users and business needs. They catch misaligned requirements early, suggest alternative approaches that save development time, and architect systems that support business growth rather than constrain it. <em>(TODO: write about this)</em></p>

<h3 id="3-it-will-reduces-hiring-and-retention-costs">3. It Will Reduces Hiring and Retention Costs</h3>

<p>Replacing a burned-out senior engineer costs 6-12 months of their salary in recruitment, onboarding, and lost productivity. A $120K engineer costs $60-120K to replace, not including the knowledge loss and project delays. Sustainable workloads prevent this expensive churn cycle.</p>

<p>Organizations with optimized manpowers workloads report 40-60% lower turnover rates in engineering teams. The savings compound: reduced recruitment costs, less onboarding overhead, and retained institutional knowledge that prevents repeated mistakes.</p>

<p>Teams with optimized workloads also achieve the same output with fewer people or deliver significantly more value with existing teams. This isn’t about layoffs, it’s about resource efficiency. A team that can deliver the same results with 20% fewer engineers or 20% more features with the same headcount creates direct cost advantages.</p>

<p>This efficiency multiplies across the organization. When one team demonstrates that sustainable practices enable higher per-person productivity, it creates a template for scaling without proportional hiring increases. Companies can grow revenue without linearly scaling engineering costs, improving unit economics and profitability.</p>

<h3 id="4-it-creates-scalable-competitive-advantage">4. It Creates Scalable Competitive Advantage</h3>

<p>While competitors burn through talent delivering rushed features, organizations with sustainable practices build systems that compound in value over time. This creates durable competitive advantages through:</p>

<ul>
  <li><strong>Organizational resilience</strong>: Teams with capacity respond to market changes and pivot quickly when conditions shift</li>
  <li><strong>Quality consistency</strong>: Maintaining standards during high-pressure periods instead of accumulating technical debt</li>
  <li><strong>Surplus capacity</strong>: Ability to support other teams and take on strategic initiatives without proportional headcount increases</li>
</ul>

<p>At LinkAja, my squad’s optimized workload didn’t mean reduced productivity, it meant surplus capacity that demonstrated organizational strength rather than resource waste.</p>

<h2 id="closing-why-this-matters">Closing: Why This Matters</h2>

<p>I think we needs to start accepting workprocess leaning (a.k.a lessening workloads) as a strategic action. We need to constantly put creating conditions where people can consistently do their best work, as our top priority along with other business purposes. When teams have breathing room, it opens up capacity, we can get strategic. We can solve harder problems. We build systems that last.Not about being soft on performance or lowering standards.</p>

<p>Everyone wins when manpowers workload drops strategically. The challenge isn’t proving this works, the results speak for themselves when you try it. The real challenge is shifting mindsets in cultures that still believe more hours equals more value.</p>

<p>The best way to go faster is: to always have a space to find out about how we can go faster.</p>]]></content><author><name>Avre Barra</name></author><summary type="html"><![CDATA[How reducing developer workload wins for everyone - less toil, better systems, and more room for meaningful work.]]></summary></entry><entry><title type="html">Struct Params and Frequent Validation in Go</title><link href="https://avrebarra.github.io/struct-params-and-frequent-validation/" rel="alternate" type="text/html" title="Struct Params and Frequent Validation in Go" /><published>2025-06-26T03:00:00+00:00</published><updated>2025-06-26T03:00:00+00:00</updated><id>https://avrebarra.github.io/struct-params-and-frequent-validation</id><content type="html" xml:base="https://avrebarra.github.io/struct-params-and-frequent-validation/"><![CDATA[<div class="text-sm text-gray-500" style="line-height: 1.5; background: repeating-linear-gradient(-45deg, #f7fafc, #f7fafc 8px, #f1f5f9 8px, #f1f5f9 16px); padding: 0.7em 1em; border-radius: 6px;">
    This piece is an adapted summary of my internal knowledge-sharing initiatives at work.
</div>

<p>When working with Go functions that accept multiple parameters, there’s an interesting approach we can explore: using struct parameters instead of positional arguments. This technique can bring clearer code and structure to our function interfaces, especially as complexity grows.</p>

<h2 id="introduction-to-struct-parameters">Introduction to Struct Parameters</h2>

<p>Struct parameters offer an alternative approach where, instead of passing a long list of positional arguments, we pass a single struct containing all the necessary data. This becomes especially helpful when functions start to require many parameters, sometimes five, six, or even more, leading to unwieldy signatures like:</p>

<h3 id="before-positional-parameters">Before: Positional Parameters</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">func</span> <span class="nx">CreateUser</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">email</span><span class="p">,</span> <span class="nx">age</span> <span class="nx">int</span><span class="p">,</span> <span class="nx">isActive</span> <span class="nx">bool</span><span class="p">,</span> <span class="nx">role</span> <span class="nx">string</span><span class="p">)</span> <span class="nx">error</span> <span class="p">{</span>
    <span class="c1">// implementation</span>
<span class="p">}</span>

<span class="c1">// Usage - with several parameters, it's easy to lose track of what each value means</span>
<span class="nl">err</span> <span class="p">:</span><span class="o">=</span> <span class="nx">CreateUser</span><span class="p">(</span>
    <span class="dl">"</span><span class="s2">John</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">john@example.com</span><span class="dl">"</span><span class="p">,</span>
    <span class="mi">30</span><span class="p">,</span>
    <span class="kc">true</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">admin</span><span class="dl">"</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div>

<p>As the number of parameters grows, it becomes increasingly difficult to keep track of what each value represents, making the code harder to read and maintain.</p>

<h3 id="after-struct-parameters">After: Struct Parameters</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">type</span> <span class="nx">CreateUserParams</span> <span class="nx">struct</span> <span class="p">{</span>
    <span class="nx">Name</span>     <span class="nx">string</span>
    <span class="nx">Email</span>    <span class="nx">string</span>
    <span class="nx">Age</span>      <span class="nx">int</span>
    <span class="nx">IsActive</span> <span class="nx">bool</span>
    <span class="nx">Role</span>     <span class="nx">string</span>
<span class="p">}</span>

<span class="nx">func</span> <span class="nx">CreateUser</span><span class="p">(</span><span class="nx">params</span> <span class="nx">CreateUserParams</span><span class="p">)</span> <span class="nx">error</span> <span class="p">{</span>
    <span class="c1">// implementation</span>
<span class="p">}</span>

<span class="c1">// Usage - struct parameters provide much clearer context</span>
<span class="nl">err</span> <span class="p">:</span><span class="o">=</span> <span class="nx">CreateUser</span><span class="p">(</span><span class="nx">CreateUserParams</span><span class="p">{</span>
    <span class="nl">Name</span><span class="p">:</span>     <span class="dl">"</span><span class="s2">John</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">Email</span><span class="p">:</span>    <span class="dl">"</span><span class="s2">john@example.com</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">Age</span><span class="p">:</span>      <span class="mi">30</span><span class="p">,</span>
    <span class="nx">IsActive</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="nx">Role</span><span class="p">:</span>     <span class="dl">"</span><span class="s2">admin</span><span class="dl">"</span><span class="p">,</span>
<span class="p">})</span>
</code></pre></div></div>

<p>The struct approach makes the code more self-documenting, as field names provide immediate context about what each value represents. This can be particularly valuable during code reviews, where reviewers can quickly understand the intent and spot any potential issues more easily. The built-in documentation aspect is another benefit - struct fields can include comments and tags, giving us better IDE support with autocomplete and parameter hints.</p>

<p>However, this approach does introduce some additional overhead. We need to define structs for each function, which means more boilerplate code. There’s also a learning curve as the team adopts these new patterns, and we need to maintain consistency in struct naming and conventions across the codebase. Of all, one notable downside of struct parameters is that, because struct fields have zero values by default, all parameters become effectively optional. This can unintentionally allow incomplete or invalid data to slip through, unlike positional parameters where required arguments are enforced by the function signature.</p>

<p>This challenge, though, can be addressed by introducing explicit validation logic for the struct fields, turning this potential pitfall into an opportunity for more robust input checking, as discussed in the next section.</p>

<h2 id="validation-as-a-natural-extension">Validation as a Natural Extension</h2>

<p>This is where struct parameters become particularly interesting, they naturally encourage better validation practices. By using struct parameters, we can take advantage of Go’s struct tags to define validation rules directly on each field. This enables us to leverage popular libraries like <a href="https://github.com/go-playground/validator"><code class="language-plaintext highlighter-rouge">go-playground/validator/v10</code></a> for declarative, reusable validation logic.</p>

<p>Struct tags make it easy to specify constraints such as required fields, length limits, or format checks, keeping validation rules close to the data they apply to. This approach can also significantly reduce the amount of repetitive <code class="language-plaintext highlighter-rouge">if</code> statements you need to write for manual validation, leading to cleaner and more maintainable code.</p>

<h3 id="validation-at-the-boundary">Validation at the Boundary</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">(</span>
    <span class="dl">"</span><span class="s2">fmt</span><span class="dl">"</span>
    <span class="dl">"</span><span class="s2">github.com/go-playground/validator/v10</span><span class="dl">"</span>
<span class="p">)</span>

<span class="kd">var</span> <span class="nx">validate</span> <span class="o">=</span> <span class="nx">validator</span><span class="p">.</span><span class="nx">New</span><span class="p">()</span>

<span class="nx">type</span> <span class="nx">CreateUserParams</span> <span class="nx">struct</span> <span class="p">{</span>
    <span class="nx">Name</span>     <span class="nx">string</span> <span class="s2">`validate:"required,min=2,max=50"`</span>
    <span class="nx">Email</span>    <span class="nx">string</span> <span class="s2">`validate:"required,email"`</span>
    <span class="nx">Age</span>      <span class="nx">int</span>    <span class="s2">`validate:"required,min=18,max=120"`</span>
    <span class="nx">IsActive</span> <span class="nx">bool</span>   <span class="s2">`validate:"-"`</span>
    <span class="nx">Role</span>     <span class="nx">string</span> <span class="s2">`validate:"required,oneof=admin user guest"`</span>
<span class="p">}</span>

<span class="nx">func</span> <span class="nx">CreateUser</span><span class="p">(</span><span class="nx">params</span> <span class="nx">CreateUserParams</span><span class="p">)</span> <span class="nx">error</span> <span class="p">{</span>
    <span class="k">if</span> <span class="nx">err</span> <span class="p">:</span><span class="o">=</span> <span class="nx">validate</span><span class="p">.</span><span class="nx">Struct</span><span class="p">(</span><span class="nx">params</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="nx">nil</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">err</span>
    <span class="p">}</span>

    <span class="c1">// implementation</span>
    <span class="k">return</span> <span class="nx">nil</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="the-value-of-more-frequent-validation">The Value of More Frequent Validation</h2>

<p>Implementing more frequent validation brings several advantages. Following the fail-fast principle, we can catch issues at the entry point rather than deep in the logic, providing clearer and more actionable error messages. This approach also helps prevent invalid data from propagating through the system, making debugging much more straightforward since we know exactly where validation failed.</p>

<p>Additionally, frequent validation acts as a safeguard against unexpected or malformed input from external users or systems. By validating inputs early, we reduce the risk of downstream errors and ensure that only well-formed data enters our core logic, improving the overall reliability and security of our applications.</p>

<h2 id="managing-unavoidable-runtime-checks-instead-of-statically-checked">Managing Unavoidable: Runtime Checks instead of Statically Checked</h2>

<p>With positional parameters, the compiler can enforce that all required arguments are provided, offering a form of static validation at compile time. In contrast, struct parameters make all fields optional by default, so missing or invalid data is only caught at runtime through explicit validation. While this shifts some safety checks from compile time to runtime, the risk is manageable if the module is using the validation correctly plus already well covered by unit tests. As long as validation logic is thoroughly tested, the benefits of clearer code and maintainability usually outweigh this trade-off.</p>

<h2 id="when-to-use-struct-parameters">When to Use Struct Parameters</h2>

<h3 id="good-candidates">Good Candidates</h3>

<ul>
  <li>Functions with 4+ parameters</li>
  <li>Functions called frequently in different contexts</li>
  <li>APIs and public interfaces</li>
  <li>Configuration-heavy functions</li>
  <li>Functions juggling with many validation criteria of its input</li>
</ul>

<h3 id="when-to-avoid">When to Avoid</h3>

<ul>
  <li>Simple functions with 1-3 parameters</li>
  <li>Internal utility/helper functions, where the added complexity of a struct isn’t justified and simplicity is preferred</li>
</ul>

<h2 id="best-practices">Best Practices</h2>

<ol>
  <li><strong>Consistent Naming</strong>: Use <code class="language-plaintext highlighter-rouge">*Params</code> suffix for parameter structs</li>
  <li><strong>Validation Methods</strong>: Implement validation call on function intros</li>
  <li><strong>Documentation</strong>: Add comments to struct fields explaining constraints</li>
  <li><strong>Zero Values</strong>: Be explicit about what zero values mean</li>
  <li><strong>Backwards Compatibility</strong>: Consider versioning for public APIs</li>
</ol>

<h2 id="conclusion">Conclusion</h2>

<p>Struct parameters combined with frequent validation create a robust foundation for maintainable Go code. While there’s an initial investment in setup and learning, the benefits of clearer code, better error handling, and improved maintainability typically outweigh the costs.</p>

<p>The key is to start small - try this pattern with new functions first, and gradually refactor existing code where it provides clear value. Remember that the goal is better code quality and developer experience, not just following a pattern for its own sake.</p>]]></content><author><name>Avre Barra</name></author><category term="golang" /><category term="best-practices" /><category term="validation" /><category term="clean-code" /><summary type="html"><![CDATA[Why struct params plus frequent validation makes Go APIs safer - patterns for clear, fail-fast code and maintainable boundaries.]]></summary></entry><entry><title type="html">Practical Concurrency in Go with errgroup</title><link href="https://avrebarra.github.io/practical-concurrency-in-go-with-errgroup/" rel="alternate" type="text/html" title="Practical Concurrency in Go with errgroup" /><published>2025-06-13T03:00:00+00:00</published><updated>2025-06-13T03:00:00+00:00</updated><id>https://avrebarra.github.io/practical-concurrency-in-go-with-errgroup</id><content type="html" xml:base="https://avrebarra.github.io/practical-concurrency-in-go-with-errgroup/"><![CDATA[<div class="text-sm text-gray-500" style="line-height: 1.5; background: repeating-linear-gradient(-45deg, #f7fafc, #f7fafc 8px, #f1f5f9 8px, #f1f5f9 16px); padding: 0.7em 1em; border-radius: 6px;">
    This piece is an adapted summary of my internal knowledge-sharing initiatives at work.
</div>

<p>We naturally think sequentially when we code―it feels intuitive, it’s easy to reason about, and it works. We write functions that call other functions, handle one task after another, and build our applications step by step. This sequential approach serves us well in most programming languages where concurrency comes with significant overhead and complexity.</p>

<p>But in some languages that support concurrency, like Golang, concurrency is cheap and accessible, making it almost wasteful to stick with purely sequential execution, especially when our code could genuinely benefit from doing multiple things at once.</p>

<p>When we have operations that don’t depend on each other, like fetching data from different APIs, processing independent files, or making multiple database queries, we’re leaving performance on the table by doing them one after another.</p>

<h2 id="introduction-to-the-sequential-bottleneck">Introduction to the Sequential Bottleneck</h2>

<p>Consider a typical scenario in a web application where we need to fetch data from multiple sources to build a response. The straightforward approach often looks like this:</p>

<h3 id="before-sequential-execution">Before: Sequential Execution</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">func</span> <span class="nx">GetDashboardData</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">userID</span> <span class="nx">string</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="nx">DashboardData</span><span class="p">,</span> <span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">profile</span><span class="p">,</span> <span class="nx">err</span> <span class="p">:</span><span class="o">=</span> <span class="nx">userService</span><span class="p">.</span><span class="nx">GetUserProfile</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">userID</span><span class="p">)</span>
    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="nx">nil</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">nil</span><span class="p">,</span> <span class="nx">err</span>
    <span class="p">}</span>

    <span class="nx">transactions</span><span class="p">,</span> <span class="nx">err</span> <span class="p">:</span><span class="o">=</span> <span class="nx">transactionService</span><span class="p">.</span><span class="nx">GetRecentTransactions</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">userID</span><span class="p">)</span>
    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="nx">nil</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">nil</span><span class="p">,</span> <span class="nx">err</span>
    <span class="p">}</span>

    <span class="nx">notifications</span><span class="p">,</span> <span class="nx">err</span> <span class="p">:</span><span class="o">=</span> <span class="nx">notificationService</span><span class="p">.</span><span class="nx">GetUnreadNotifications</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">userID</span><span class="p">)</span>
    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="nx">nil</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">nil</span><span class="p">,</span> <span class="nx">err</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="o">&amp;</span><span class="nx">DashboardData</span><span class="p">{</span>
        <span class="nl">Profile</span><span class="p">:</span>       <span class="nx">profile</span><span class="p">,</span>
        <span class="nx">Transactions</span><span class="p">:</span>  <span class="nx">transactions</span><span class="p">,</span>
        <span class="nx">Notifications</span><span class="p">:</span> <span class="nx">notifications</span><span class="p">,</span>
    <span class="p">},</span> <span class="nx">nil</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This approach is clear and easy to understand, but it has a significant drawback: each operation must complete before the next one can begin. If each service call takes 100ms, our total response time is at least 300ms, even though these operations are completely independent of each other.</p>

<h3 id="after-concurrent-execution-with-errgroup">After: Concurrent Execution with errgroup</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">func</span> <span class="nx">GetDashboardData</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">userID</span> <span class="nx">string</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="nx">DashboardData</span><span class="p">,</span> <span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">g</span><span class="p">,</span> <span class="nx">ctx</span> <span class="p">:</span><span class="o">=</span> <span class="nx">errgroup</span><span class="p">.</span><span class="nx">WithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span>

    <span class="kd">var</span> <span class="p">(</span>
        <span class="nx">profile</span>       <span class="o">*</span><span class="nx">UserProfile</span>
        <span class="nx">transactions</span>  <span class="p">[]</span><span class="o">*</span><span class="nx">Transaction</span>
        <span class="nx">notifications</span> <span class="p">[]</span><span class="o">*</span><span class="nx">Notification</span>
    <span class="p">)</span>

    <span class="nx">g</span><span class="p">.</span><span class="nx">Go</span><span class="p">(</span><span class="nx">func</span><span class="p">()</span> <span class="nx">error</span> <span class="p">{</span>
        <span class="kd">var</span> <span class="nx">err</span> <span class="nx">error</span>
        <span class="nx">profile</span><span class="p">,</span> <span class="nx">err</span> <span class="o">=</span> <span class="nx">userService</span><span class="p">.</span><span class="nx">GetUserProfile</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">userID</span><span class="p">)</span>
        <span class="k">return</span> <span class="nx">err</span>
    <span class="p">})</span>

    <span class="nx">g</span><span class="p">.</span><span class="nx">Go</span><span class="p">(</span><span class="nx">func</span><span class="p">()</span> <span class="nx">error</span> <span class="p">{</span>
        <span class="kd">var</span> <span class="nx">err</span> <span class="nx">error</span>
        <span class="nx">transactions</span><span class="p">,</span> <span class="nx">err</span> <span class="o">=</span> <span class="nx">transactionService</span><span class="p">.</span><span class="nx">GetRecentTransactions</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">userID</span><span class="p">)</span>
        <span class="k">return</span> <span class="nx">err</span>
    <span class="p">})</span>

    <span class="nx">g</span><span class="p">.</span><span class="nx">Go</span><span class="p">(</span><span class="nx">func</span><span class="p">()</span> <span class="nx">error</span> <span class="p">{</span>
        <span class="kd">var</span> <span class="nx">err</span> <span class="nx">error</span>
        <span class="nx">notifications</span><span class="p">,</span> <span class="nx">err</span> <span class="o">=</span> <span class="nx">notificationService</span><span class="p">.</span><span class="nx">GetUnreadNotifications</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">userID</span><span class="p">)</span>
        <span class="k">return</span> <span class="nx">err</span>
    <span class="p">})</span>

    <span class="k">if</span> <span class="nx">err</span> <span class="p">:</span><span class="o">=</span> <span class="nx">g</span><span class="p">.</span><span class="nx">Wait</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="nx">nil</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">nil</span><span class="p">,</span> <span class="nx">err</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="o">&amp;</span><span class="nx">DashboardData</span><span class="p">{</span>
        <span class="nl">Profile</span><span class="p">:</span>       <span class="nx">profile</span><span class="p">,</span>
        <span class="nx">Transactions</span><span class="p">:</span>  <span class="nx">transactions</span><span class="p">,</span>
        <span class="nx">Notifications</span><span class="p">:</span> <span class="nx">notifications</span><span class="p">,</span>
    <span class="p">},</span> <span class="nx">nil</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With this concurrent approach, all three operations run simultaneously. Our total response time becomes the duration of the slowest operation rather than the sum of all operations, potentially reducing our response time from 300ms to just over 100ms.</p>

<h2 id="why-errgroup-over-raw-goroutines">Why errgroup Over Raw Goroutines</h2>

<p>While we could achieve concurrency using raw goroutines and channels, <code class="language-plaintext highlighter-rouge">errgroup</code> provides several advantages that make it the preferred choice for this pattern:</p>

<h3 id="a-built-in-error-handling">A. Built-in Error Handling</h3>

<p>The errgroup automatically cancels the context when any goroutine returns an error, causing all other operations to be cancelled as well. This prevents unnecessary work and ensures fast failure detection.</p>

<h3 id="b-context-propagation">B. Context Propagation</h3>

<p>The <code class="language-plaintext highlighter-rouge">errgroup.WithContext()</code> function creates a derived context that gets cancelled when the first error occurs, allowing all goroutines to respond appropriately to cancellation signals.</p>

<h3 id="c-simplified-synchronization">C. Simplified Synchronization</h3>

<p>No need to manually manage channels or wait groups, the <code class="language-plaintext highlighter-rouge">Wait()</code> method handles all synchronization logic and returns the first error encountered, if any.</p>

<h2 id="understanding-the-performance-impact">Understanding the Performance Impact</h2>

<p>The performance benefits of concurrent execution become more pronounced as the number of independent operations increases or as the latency of individual operations grows. Here’s how the math works:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Sequential Timing
Operation A: 100ms
Operation B: 150ms
Operation C: 80ms
Total: 330ms

// Concurrent Timing
All operations start simultaneously
Total: max(100ms, 150ms, 80ms) = 150ms
</code></pre></div></div>

<p>This represents a 55% reduction in total execution time, a significant improvement that users will notice, especially in latency-sensitive applications.</p>

<h2 id="common-patterns-and-best-practices">Common Patterns and Best Practices</h2>

<p>It’s important to recognize when and how to apply concurrency with <code class="language-plaintext highlighter-rouge">errgroup</code>. Not every problem benefits from parallel execution, but for the right scenarios, <code class="language-plaintext highlighter-rouge">errgroup</code> can dramatically simplify your code and boost performance. Below are some practical patterns and best practices for using <code class="language-plaintext highlighter-rouge">errgroup</code> effectively in real-world Go applications.</p>

<h3 id="a-pattern-1-independent-data-fetching">A. Pattern 1: Independent Data Fetching</h3>

<p>This is the most common use case, where you need to gather data from multiple sources that don’t depend on each other.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">g</span><span class="p">,</span> <span class="nx">ctx</span> <span class="p">:</span><span class="o">=</span> <span class="nx">errgroup</span><span class="p">.</span><span class="nx">WithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">data1</span><span class="p">,</span> <span class="nx">data2</span><span class="p">,</span> <span class="nx">data3</span> <span class="nx">SomeType</span>

<span class="nx">g</span><span class="p">.</span><span class="nx">Go</span><span class="p">(</span><span class="nx">func</span><span class="p">()</span> <span class="nx">error</span> <span class="p">{</span> <span class="cm">/* fetch data1 */</span> <span class="p">})</span>
<span class="nx">g</span><span class="p">.</span><span class="nx">Go</span><span class="p">(</span><span class="nx">func</span><span class="p">()</span> <span class="nx">error</span> <span class="p">{</span> <span class="cm">/* fetch data2 */</span> <span class="p">})</span>
<span class="nx">g</span><span class="p">.</span><span class="nx">Go</span><span class="p">(</span><span class="nx">func</span><span class="p">()</span> <span class="nx">error</span> <span class="p">{</span> <span class="cm">/* fetch data3 */</span> <span class="p">})</span>

<span class="k">if</span> <span class="nx">err</span> <span class="p">:</span><span class="o">=</span> <span class="nx">g</span><span class="p">.</span><span class="nx">Wait</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="nx">nil</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">err</span>
<span class="p">}</span>
<span class="c1">// Use data1, data2, data3</span>
</code></pre></div></div>

<h3 id="b-pattern-2-processing-independent-items">B. Pattern 2: Processing Independent Items</h3>

<p>When you have a collection of items that can be processed independently:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">g</span><span class="p">,</span> <span class="nx">ctx</span> <span class="p">:</span><span class="o">=</span> <span class="nx">errgroup</span><span class="p">.</span><span class="nx">WithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span>

<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">item</span> <span class="p">:</span><span class="o">=</span> <span class="nx">range</span> <span class="nx">items</span> <span class="p">{</span>
    <span class="nl">item</span> <span class="p">:</span><span class="o">=</span> <span class="nx">item</span> <span class="c1">// Capture loop variable</span>
    <span class="nx">g</span><span class="p">.</span><span class="nx">Go</span><span class="p">(</span><span class="nx">func</span><span class="p">()</span> <span class="nx">error</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">processItem</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">item</span><span class="p">)</span>
    <span class="p">})</span>
<span class="p">}</span>

<span class="k">return</span> <span class="nx">g</span><span class="p">.</span><span class="nx">Wait</span><span class="p">()</span>
</code></pre></div></div>

<h3 id="c-pattern-3-limited-concurrency">C. Pattern 3: Limited Concurrency</h3>

<p>For scenarios where you want to limit the number of concurrent operations:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Create a semaphore to limit concurrency</span>
<span class="nx">semaphore</span> <span class="p">:</span><span class="o">=</span> <span class="nx">make</span><span class="p">(</span><span class="nx">chan</span> <span class="nx">struct</span><span class="p">{},</span> <span class="nx">maxConcurrency</span><span class="p">)</span>

<span class="nx">g</span><span class="p">,</span> <span class="nx">ctx</span> <span class="p">:</span><span class="o">=</span> <span class="nx">errgroup</span><span class="p">.</span><span class="nx">WithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span>

<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">item</span> <span class="p">:</span><span class="o">=</span> <span class="nx">range</span> <span class="nx">items</span> <span class="p">{</span>
    <span class="nl">item</span> <span class="p">:</span><span class="o">=</span> <span class="nx">item</span>
    <span class="nx">g</span><span class="p">.</span><span class="nx">Go</span><span class="p">(</span><span class="nx">func</span><span class="p">()</span> <span class="nx">error</span> <span class="p">{</span>
        <span class="nx">semaphore</span> <span class="o">&lt;-</span> <span class="nx">struct</span><span class="p">{}{}</span> <span class="c1">// Acquire</span>
        <span class="nx">defer</span> <span class="nx">func</span><span class="p">()</span> <span class="p">{</span> <span class="o">&lt;-</span><span class="nx">semaphore</span> <span class="p">}()</span> <span class="c1">// Release</span>

        <span class="k">return</span> <span class="nx">processItem</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">item</span><span class="p">)</span>
    <span class="p">})</span>
<span class="p">}</span>

<span class="k">return</span> <span class="nx">g</span><span class="p">.</span><span class="nx">Wait</span><span class="p">()</span>
</code></pre></div></div>

<h2 id="when-to-use-errgroup">When to Use errgroup</h2>

<h3 id="good-candidates">Good Candidates</h3>

<ul>
  <li>Multiple independent API calls or database queries</li>
  <li>Processing collections where items don’t depend on each other</li>
  <li>Validation operations that can run in parallel</li>
  <li>File I/O operations that can be performed simultaneously</li>
</ul>

<h3 id="when-to-avoid">When to Avoid</h3>

<ul>
  <li>Operations that depend on results from other operations</li>
  <li>CPU-intensive tasks that would benefit from worker pools instead</li>
  <li>Simple, fast operations where the concurrency overhead isn’t worth it</li>
  <li>Operations that share mutable state without proper synchronization</li>
</ul>

<h2 id="managing-the-risks">Managing the Risks</h2>

<p>While errgroup helps mitigate many concurrency risks, there are still important considerations:</p>

<h3 id="i-shared-state">I. Shared State</h3>

<p>Ensure that any shared variables are properly protected. In the errgroup patterns shown above, each goroutine writes to a different variable, avoiding race conditions.</p>

<h3 id="ii-resource-limits">II. Resource Limits</h3>

<p>Be mindful of resource consumption. Running too many concurrent operations can overwhelm databases, APIs, or network connections. Use the limited concurrency pattern when needed.</p>

<h3 id="iii-error-context">III. Error Context</h3>

<p>Since errgroup returns only the first error encountered, ensure your error messages provide enough context to understand which operation failed.</p>

<h2 id="conclusion">Conclusion</h2>

<p>The <code class="language-plaintext highlighter-rouge">errgroup</code> package offers a practical and reliable way to add concurrency to Go applications, reducing complexity compared to managing raw goroutines and channels directly. By identifying independent operations, especially those involving I/O or external services, you can often achieve significant performance gains with minimal code changes.</p>

<p>To get started, begin by reviewing your code for sequential tasks that don’t rely on each other. Where appropriate, use <code class="language-plaintext highlighter-rouge">errgroup</code> to run them concurrently, improving responsiveness and resource utilization. With careful application and attention to best practices, <code class="language-plaintext highlighter-rouge">errgroup</code> can help you write faster, more maintainable, and robust Go programs.</p>]]></content><author><name>Avre Barra</name></author><category term="golang" /><category term="concurrency" /><category term="errgroup" /><category term="performance" /><summary type="html"><![CDATA[Practical Go concurrency with errgroup: run independent tasks in parallel, handle errors cleanly, and remove sequential bottlenecks.]]></summary></entry><entry><title type="html">Journal: Living Below Means, Living Boring</title><link href="https://avrebarra.github.io/personal-essay-living-below-means-living-boring/" rel="alternate" type="text/html" title="Journal: Living Below Means, Living Boring" /><published>2025-05-15T00:00:00+00:00</published><updated>2025-05-15T00:00:00+00:00</updated><id>https://avrebarra.github.io/personal-essay-living-below-means-living-boring</id><content type="html" xml:base="https://avrebarra.github.io/personal-essay-living-below-means-living-boring/"><![CDATA[<p><em>“What is life if it’s not living big?”</em></p>

<p>Pinjol debts and financial chaos used to sound like background noise, stuff that happened to “other people” for me. Lately, though, those headlines are landing closer; it’s friends, neighbors, people I know.</p>

<p>I’m kind of surprised, <em>well, not really</em>, but it hits me that it’s closer than I thought. I’ve been hearing stories of people struggling, and pardon me for judging but I can’t help but think maybe it has something to do with how they live. Like wanting to live big. Like maybe we’re chasing too much. Or maybe I’m wrong. I don’t know. But it makes me think lately.</p>

<p>I get it, people want to feel alive. We want joy, fun, excitement. And yeah, what is life if it’s not lively, right? But I’m starting to question what “lively” really means. Social media kind of screws with our heads. It makes anything less than flashy look like failure. We all know, that’s messed up.</p>

<p>Lately I start to think that maybe what makes lives lively are just about the margins, the gaps. What makes life memorable are those gaps between moments. Like that time we went from <em>meh</em> 😒 to <em>WOWZA</em> 🤩. That’s one of life isn’t it? Those changes of feelings, those gaps.</p>

<p>Now I kinda think, sure, those going ups give us gaps, that sweet contrast. But maybe it’s also okay to not endlessly chase the higher and higher gaps. Maybe we don’t need to live life like we’re always chasing highs just to feel like we’re living. Living below our means, embracing the downs too, maybe that’s how we last. We can still enjoy the ups when they come, but we don’t have to gain something every day. <em>We’re earthlings. The sky is vast, limitless, but we’re bound by how long our limbs can hold us up to keep our head over the ground.</em> We can jump, true. But let’s not recklessly catapult ourselves into the atmosphere.</p>

<p>I’m the kind of person who finds peace in things that others might call mundane <del><em>(wn: oh how self righteous it makes me sick too haha, but bear with me)</em></del>. I think people around me also knew that I can be very boring. Heck I am so boring. I stare at leaves. I lie on the floor and look at clouds moving, for tens of minutes. I sit alone in a supermarket corner eating snacks like it’s a ritual. I swim by myself. I motorbike around aimlessly and talk to myself. I can stay on a place for the whole day without talking to anyone or laughing along or listening to anything. That is a boring person by definition ain’t it? Sometimes I even wonder to myself too―<em>wah, is this really me? just this? nothing fancy to show off?</em>―and at times it made me feel a little feeble too. <em>Ain’t I look miserable? Like maybe I need to live more?</em></p>

<p>But lately, I’ve started to feel grateful for this kind of life. I’m glad that it makes me content. I’m not saying it’s the right one. I’m not trying to self-justify. But this demeanor, this plainness, has quietly kept me safe from some of the risks people around me are currently facing. At least for now. <em>Alhamdulillah ala kulli haal</em>. Or maybe I just got lucky? Or still got lucky? I don’t know. I’m not advocating to live like me. I don’t even know if this is good for me in the long run. But if nothing else, maybe <em>hey use me as lower baseline!</em> As reminder that there are people like me, who live pretty boring lives, and (can survive trying to) being content with it.</p>

<p>So please live below means, live boring.</p>

<p>No, I take that back. Just put it in mind as a possible way to take, to live below means and boring. Trying to keep up with those crazy, flashy standards by spending beyond what we can afford is not a good idea―and no, ability to pinjol and lendings aren’t counted as what we can afford. Be boring and don’t be reckless. It’s okay to humble things down sometimes, we’ll still live. Maybe without those crazy adrenalines, but still living, still breathing. We’ll still feel things. Not social media worthy, but we’ll still have moments.</p>

<p>If you worry about being seen as simple or boring, sneer at me, or sneer at someone else like me… no, sneer at me. Use us as your lower baseline. Even if you live a plain, quiet life, there’s at least one person in your circle with even more boring life. And that’s okay.</p>

<p>Just don’t get caught up in those problems for that reason, will you? <em>Because I overthink a lot, and damn I wish I never have to overthink about you too.</em></p>]]></content><author><name>Avre Barra</name></author><summary type="html"><![CDATA[Why living below your means isn't boring - how margins and gaps create a more alive life than chasing luxury and flash.]]></summary></entry><entry><title type="html">Journal: Happy New Year! 🎉</title><link href="https://avrebarra.github.io/happy-new-year/" rel="alternate" type="text/html" title="Journal: Happy New Year! 🎉" /><published>2024-01-05T00:00:00+00:00</published><updated>2024-01-05T00:00:00+00:00</updated><id>https://avrebarra.github.io/happy-new-year</id><content type="html" xml:base="https://avrebarra.github.io/happy-new-year/"><![CDATA[<p>🎉</p>

<p>Hello again, and HAPPY NEW YEAR! Feliz Ano Novo!</p>

<p>May all our wishes come true this year!</p>

<h2 id="reflecting-back-2023">Reflecting back 2023</h2>

<p>Reflecting on 2023, it was quite a rollercoaster, as some friends experienced significant ups and downs. A friend who was once struggling with suicidal thoughts unexpectedly turned joyful and better. One marriage turned against each other due to a simple miscommunication, leading to irreparable consequences not because of the problem itself but due to their reactions. Another new marriages, another fistfights, another event failed miserably, and some football competitions. It was wild.</p>

<p>As for me? To be frank 2023 for me stands out as the best year since like 2018, in term of self-fulfillment.</p>

<p>From 2018 to years after, sure I did got career and skill improvements in those years, but honestly life felt empty and boring especially during the COVID years with bad health and habits. I don’t really learn anything new those years so yeah life felt kinda boring like we’re just being floating in a shallow slow current.</p>

<p>In 2023 though, in Q4, I decided to take a fresh start, jumping into the unknown sea. Which probably transaltes to people and swimming pool haha. I saw many dramas arose. Some are pretty bitter and emotional dramas, but seeing back into those boring years, somehow I find the bitterness are kinda refreshing haha. All in all, I can say I’m happy with 2023.</p>

<h2 id="for-those-still-having-hard-times">For those still having hard times</h2>

<p>To those who faced challenges in 2023, keep pushing through. The tech winter and numerous layoffs may have impacted families, but I hope you regain everything and bounce back even stronger.</p>

<p>While I don’t see New Year as a massive milestone (tbh its just another Q4 to another Q1), but it’s still a new empty page flipped upon us.</p>

<p>Let’s take a pen.</p>

<p>Let’s write many good things on this page.</p>

<p><em>PS. I really intended to write a technical article, but alas the New Year greeting sway me a detour wkw</em></p>]]></content><author><name>Avre Barra</name></author><summary type="html"><![CDATA[Reflecting on a rollercoaster 2023 that became my most fulfilling year since 2018, and quiet hopes for 2024 ahead.]]></summary></entry><entry><title type="html">Journal: A Small Note On Organizing People</title><link href="https://avrebarra.github.io/a-small-note-on-organizing-people/" rel="alternate" type="text/html" title="Journal: A Small Note On Organizing People" /><published>2023-11-30T00:00:00+00:00</published><updated>2023-11-30T00:00:00+00:00</updated><id>https://avrebarra.github.io/a-small-note-on-organizing-people</id><content type="html" xml:base="https://avrebarra.github.io/a-small-note-on-organizing-people/"><![CDATA[<p>So, I have decided to take a career break.</p>

<p>Looking back, it was a very long deliberate heavy thinking back then that brought me into this decision. It’s a difficult decision, there are some less pleasant aspects to it, but overall, I’m quite satisfied with what I’ve gained in exchange.</p>

<p>During this free time, I’ve embarked on activities I couldn’t pursue due to work commitments: learning swimming, playing guitar, reading, writing, reconnecting with old friends, exploring AI/ML, and even trying mountaineering.</p>

<h2 id="youth-leader-at-local-mosque">Youth Leader at Local Mosque</h2>

<p>Just recently, I was appointed as a Youth Leader at the local mosque. It revolves around making young lads and lass to go to mosque to pray and learn Quran and Hadist and such.</p>

<p>In this break, with all these free times I have, I found myself getting more involved in this role. This and that, not so long after, I realized one thing: The work I do as leader now is kinda more easier than before I took a break.</p>

<p>A little context on what the old conditions were: previously, everything felt like a solitary effort, which is fine because it’s actually did not come with demanding tasks. But I can’t say that I hoped some different results on this leadings. Things were quite time taxing. I can get things done but honestly feel like I’m working by myself all the time. I took some team decision, in various way, but not many are really committed to it. Things are going good but I honestly think it could be better. However, upon my break, everything feels smoother, I can really feel people backing me.</p>

<p>It initially puzzled me, but when I traced back, I’ve come to understand the clear significance of something I hadn’t done before.</p>

<h2 id="treating-people-like-people">Treating People Like People</h2>

<p>Yes, duh. I just, treated people more like people. I spent time together and wasted time together. Honestly aren’t that much hassle and tbh I could’ve also did it before I had a break. But I admit it was totally different than when I first took on the leadership role. My approach was: Let’s make this youth group member more systematic and tidy, creating procedures and helping members learn various skills. I wanted the organization to function smoothly even in my absence. Just like what I did at my professional work. I organize I taught people ideas I delegate things. That is idealistic notion. That never materialized.</p>

<p>But when I started spending more time together its just that I can kinda see the rewards are splattered around me.</p>

<h2 id="non-professional-vs-professional-organization">Non-Professional vs Professional Organization</h2>

<p>Today I’ve come to realize again the basic that people are just, well, people. Unlike machines, everyone have their own dynamics and desires. Previously, I thought I could organize the organization like I did in my professional life, treating people (which includes me) as nodes in our big big machine, which worked pretty well without a problem (probably because thats pretty normal in the field).</p>

<p>However, in this nonprofessional sector, where motivations are not solely driven by payroll, where things are felt more raw, pure, and honest, this approach is flawed. People are really people; they are unique individuals with their own motivations and desires that I have to get closer to get to know.</p>

<p>This small realization will be some nail in the wood for me. Affirming some ideas that actually was already there but not really seen clearly. To not treat people as just dots, just functions providing predictable outputs based on inputs. That they have their own dynamics, reasons, and motivations, which may not always align with what we expect.</p>

<p>In professional world, individuals often can function flawlessly as if they were a cog in a machine. However, we must acknowledge and respect the human aspect, considering their needs, plans, and desires. Although abstration of organization as an abstraction has its merit, we have to know that it probably just a facade, of a pure and honest organization where humans are actually needed to be treated as human. Whoa, beautiful.</p>

<h2 id="closing-words">Closing Words</h2>

<p>Mind it, I was a shy anxious introvert that never really involved in leadership before my professional experience in leadership. All these small things are small things but still funny to me, considering how I experienced formal leadership before the non-formal one. It’s kinda late, but I’m very grateful for these new perspectives mending events.</p>

<p>Revisiting back the title, in conclusion, my break has again fortify me a valuable lesson: to lead and organize people, it’s essential to still embrace their humanity. Recognizing and respecting individual dynamics, motivations, and desires creates a more harmonious and fulfilling environment, leading to greater success in our collective endeavors.</p>

<p>If I have to take one quote: the magic will happens when we treat all people as unique individuals. Then, let’s.</p>]]></content><author><name>Avre Barra</name></author><category term="journal" /><summary type="html"><![CDATA[What leading a youth group at a mosque taught me about organizing people - ownership, trust, and making coordination feel lightweight.]]></summary></entry><entry><title type="html">Hidden Layers in System Abstraction - Unveiling an Onion</title><link href="https://avrebarra.github.io/hidden-layers-in-system-abstraction/" rel="alternate" type="text/html" title="Hidden Layers in System Abstraction - Unveiling an Onion" /><published>2023-10-13T00:00:00+00:00</published><updated>2023-10-13T00:00:00+00:00</updated><id>https://avrebarra.github.io/hidden-layers-in-system-abstraction</id><content type="html" xml:base="https://avrebarra.github.io/hidden-layers-in-system-abstraction/"><![CDATA[<p>Building systems sometimes can be a wild ride.</p>

<p>Ever been driven frustrated by something seemed simple, only to discover hidden complexities beneath?</p>

<p>I’ve been on quite a journey myself. Let’s dive into my experiences on building what I called an onion system.</p>

<h2 id="the-first-attempt-loyalty-point-async-system">The First Attempt: Loyalty Point Async System</h2>

<p>Back in my Bukalapak days, loyalty points system was one of projects I have to work on. The mission was clear: process transactions asynchronously and calculate the loyalty points. The core system would send a signal to our microservices, and we’d evaluate and whip up some loyalty points for customer.</p>

<p>Being a newbie, we thought simple - we thought loyalty points were our one and only big word there. Not too big scope, all figured out. So we jumped right in and developed that async system with some database, backend, and RabbitMQ for messaging. We had issues, we fixed, the task was completed and running without major hiccups. But there was a nagging feeling - something was off with the system layout I know, but I couldn’t quite understand what was it.</p>

<h2 id="the-second-attempt-bulk-disbursement-system">The Second Attempt: Bulk Disbursement System</h2>

<p>Fast forward to LinkAja. Again here I had to cook up another async system. Prakerja bulk-async fund disbursement system. A hot government program at that time.</p>

<p>This time, it was like playing in the big leagues. This system is a big system planned to involve billions Rupiah circulated everyday. High traffic. High reputational stake.</p>

<p>At start, we analyzed and I thought, “It’s all about ‘funds’ again, right? Just like what I did back then and add some bulk inputs.” We split the work over several sprints, trying to ensure progress step by step. And as usual, there are changes on the middle of work, nothing unexpected. We thought.</p>

<p>But boy, the challenge was huge. Somehow every minor requirement requires boatload of adjustment all over the codebase. It felt like a struggle to make things developing incrementally. Feels like we lost progress for every changes coming.</p>

<p>It’s a shaky design. I don’t want to rely on this ticking timebomb, I said.</p>

<h2 id="the-revelation">The Revelation</h2>

<p>So we retook a deep dive on the design. The grand revelation hit us: bulk-async-disbursement system wasn’t a solo act; it was a three-layered system. After analysis we found the lining of three different system involved: a disbursement system, wrapped up by an async system, and then bundled inside a bulk processing system.</p>

<p>Treating them as one was like trying to detangle a knotted mess. Each piece had its role, but put in another’s piece, and when one went awry, it felt like trying to fix a jumbled orchestra. That’s when we knew we did it wrong and we have to fix the abstractions ground up. The interconnectedness was so intricate that untangling one piece without pulling apart everything proved to be quite the challenge.</p>

<p>So in short, we sprinted and tried to construct the new abstraction. Splitting one work into three works. Renegotiating timelines. Reexplaining the work scopes.</p>

<p>We built those subsystems:</p>

<ul>
  <li>Started with the disbursement system: Ensuring a disbursement can perform perfectly by itself.</li>
  <li>Then moved on to the asynchronous system layer: Adding things like status checks, cancellations, callbacks, and all that jazz)</li>
  <li>Finally, we tackled the bulk system scheduling.</li>
</ul>

<p>And as expected, everything fell into place. Developments ran smoothly, progress tracked accordingly, bug fixes are blisses. When we stepped back to admire our handiwork, we realized yep it was a giant.</p>

<p>We couldn’t help but chuckle at our previous audacity, thinking we could squeeze it into a tiny box.</p>

<h2 id="the-lessons">The Lessons</h2>

<p>This story taught us one cool lesson. You gotta look beneath the surface and not be fooled by the ‘sweet’ single-domain facade. Don’t judge a system just by its name. Sometimes, that one sweet domain dances with unnamed but funky mechanics. Here I was talking about bulk and async. But you have to be aware too about things like data encryptions, notification, content management, or event publishing acrobatics.</p>

<p>Even if a system looks like a neat package with a single entry and exit, it might be an onion - layers upon layers. So, don’t shy away from peeling those layers and revealing the complexities within. This wisdom is your secret weapon when you face future complex systems. Stay sharp and don’t underestimate what’s under the hood.</p>

<p>You could be looking at one intricate onion.</p>]]></content><author><name>Avre Barra</name></author><category term="tech" /><summary type="html"><![CDATA[Hidden layers in system abstraction: how onions, leaky abstractions, and boundaries shape resilient, understandable software.]]></summary></entry></feed>