<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Selective Creativity</title>
    <link>https://blog.sebastiansastre.co/</link>
    <description>Recent content on Selective Creativity</description>
    <generator>Hugo -- 0.154.5</generator>
    <language>en-us</language>
    <lastBuildDate>Fri, 17 Apr 2026 00:00:00 -0300</lastBuildDate>
    <atom:link href="https://blog.sebastiansastre.co/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>The Half of CI We Forgot to Automate</title>
      <link>https://blog.sebastiansastre.co/posts/the-half-of-ci-we-forgot-to-automate/</link>
      <pubDate>Fri, 17 Apr 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/the-half-of-ci-we-forgot-to-automate/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/mission-control-go-no-go.jpg" type="image/jpeg" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/mission-control-go-no-go.jpg" alt=""" loading="lazy" /></figure><p>A new aircraft does not get cleared to fly because its blueprint is internally consistent. It gets cleared because someone strapped it to a rig, ran it through scripted abuse, instrumented every joint, and — after hours and hours of tests — produced evidence that the design survives a controlled approximation of the real world. That ritual has a name in aerospace and motorsport — <em>shakedown</em> — and it exists because the cost of guessing is paid in catastrophic failures, irreparable damage to human lives, and the heavy material losses that follow.</p>
<p>Software shipped to production has the same shape of decision and almost none of the same ritual. We tag, we deploy, we wait. Go or no-go.</p>
<p>Here is an uncomfortable observation. As an industry, we automated the cheap half of trust. Types check. Schemas validate. Contracts get signed off in OpenAPI. Unit tests are green. Coverage is up and to the right. Every one of those is a statement about the <em>symbolic</em> world of the codebase — the world where our abstractions agree with each other. And that world is, by construction, the world that is easy to automatically certify for correctness.</p>
<p>Symbols can sprawl into endless glosses; evidence stays finite — the same map-versus-ground tension as in <a href="https://blog.sebastiansastre.co/posts/the-relative-trap/">The Relative Trap</a>. Here the focus is narrower: what we automated in the pipeline, and what still has to meet reality on go/no-go.</p>
<p>The expensive half — proving the system behaves the way we intend when wired to real infrastructure, real sequences, real faults, real timing — we still answer with a vibe.</p>
<p>We answer it in war rooms after the fact.</p>
<p>We answer it with a senior engineer who has &ldquo;a feeling&rdquo; about the merge.</p>
<p>We answer it with a staging environment that nobody fully trusts, and a rollback plan we hope we don&rsquo;t have to use.</p>
<p>That&rsquo;s what we compensate for by not deploying on Fridays.</p>
<p>The lesson is narrower and harder than &ldquo;test more.&rdquo;</p>
<p>That mission control go/no-go answer belongs to a <em>category</em> of decision the toolchain has not earned the right to make yet.</p>
<p>Pipelines today can tell you the code is consistent with itself. They cannot tell you, with reproducible evidence, that the system survives the part of reality you actually care about — the broker that stalls, the migration that races the seed, the consumer that lags, the dependency that flaps.</p>
<p>Until that evidence is as cheap to collect as a unit test is to write, we will keep calling faith &ldquo;rigor&rdquo; and paying the difference in incidents, postmortems, and quietly eroded customer trust.</p>
<p>The next leverage point in CI is not faster tests or smarter linters or another contract format. It is <em>realistic</em> tests — scripted, repeatable, instrumented, boring to run — that turn &ldquo;I think we&rsquo;re safe to ship&rdquo; into an artifact a regulator, a teammate, or a future-you can replay. The teams that close that gap will stop arguing about whether AI wrote the code or a human did, because the question will have moved. The question will be: <em>show me the evidence.</em></p>
<p>Right now, for most teams, the honest answer is that there isn&rsquo;t any.</p>
<p>That is the gap.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/mission-control-go-no-go.jpg" alt=""" loading="lazy" /></figure><p>A new aircraft does not get cleared to fly because its blueprint is internally consistent. It gets cleared because someone strapped it to a rig, ran it through scripted abuse, instrumented every joint, and — after hours and hours of tests — produced evidence that the design survives a controlled approximation of the real world. That ritual has a name in aerospace and motorsport — <em>shakedown</em> — and it exists because the cost of guessing is paid in catastrophic failures, irreparable damage to human lives, and the heavy material losses that follow.</p>
<p>Software shipped to production has the same shape of decision and almost none of the same ritual. We tag, we deploy, we wait. Go or no-go.</p>
<p>Here is an uncomfortable observation. As an industry, we automated the cheap half of trust. Types check. Schemas validate. Contracts get signed off in OpenAPI. Unit tests are green. Coverage is up and to the right. Every one of those is a statement about the <em>symbolic</em> world of the codebase — the world where our abstractions agree with each other. And that world is, by construction, the world that is easy to automatically certify for correctness.</p>
<p>Symbols can sprawl into endless glosses; evidence stays finite — the same map-versus-ground tension as in <a href="https://blog.sebastiansastre.co/posts/the-relative-trap/">The Relative Trap</a>. Here the focus is narrower: what we automated in the pipeline, and what still has to meet reality on go/no-go.</p>
<p>The expensive half — proving the system behaves the way we intend when wired to real infrastructure, real sequences, real faults, real timing — we still answer with a vibe.</p>
<p>We answer it in war rooms after the fact.</p>
<p>We answer it with a senior engineer who has &ldquo;a feeling&rdquo; about the merge.</p>
<p>We answer it with a staging environment that nobody fully trusts, and a rollback plan we hope we don&rsquo;t have to use.</p>
<p>That&rsquo;s what we compensate for by not deploying on Fridays.</p>
<p>The lesson is narrower and harder than &ldquo;test more.&rdquo;</p>
<p>That mission control go/no-go answer belongs to a <em>category</em> of decision the toolchain has not earned the right to make yet.</p>
<p>Pipelines today can tell you the code is consistent with itself. They cannot tell you, with reproducible evidence, that the system survives the part of reality you actually care about — the broker that stalls, the migration that races the seed, the consumer that lags, the dependency that flaps.</p>
<p>Until that evidence is as cheap to collect as a unit test is to write, we will keep calling faith &ldquo;rigor&rdquo; and paying the difference in incidents, postmortems, and quietly eroded customer trust.</p>
<p>The next leverage point in CI is not faster tests or smarter linters or another contract format. It is <em>realistic</em> tests — scripted, repeatable, instrumented, boring to run — that turn &ldquo;I think we&rsquo;re safe to ship&rdquo; into an artifact a regulator, a teammate, or a future-you can replay. The teams that close that gap will stop arguing about whether AI wrote the code or a human did, because the question will have moved. The question will be: <em>show me the evidence.</em></p>
<p>Right now, for most teams, the honest answer is that there isn&rsquo;t any.</p>
<p>That is the gap.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Cost of Indirection in Rust</title>
      <link>https://blog.sebastiansastre.co/posts/cost-of-indirection-in-rust/</link>
      <pubDate>Sat, 07 Mar 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/cost-of-indirection-in-rust/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/cost-of-indirection-rust.jpg" type="image/jpeg" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/cost-of-indirection-rust.jpg" alt=""" loading="lazy" /></figure><p>We&rsquo;ve all heard the warning: &ldquo;Every extra function call adds overhead. Inline it!&rdquo; In Rust async code, that worry is almost always false.</p>
<p>Look at the size of that arm:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">handle_event</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">event</span>: <span class="nc">Event</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">match</span><span class="w"> </span><span class="n">event</span><span class="p">.</span><span class="n">kind</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">EventKind</span>::<span class="n">Suspend</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// … &gt; 20 lines of app behavior … 
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// … other arms …
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>The author had hot context in his head when he wrote it and now you&rsquo;ll see his bias to justify it. And make others justify it too — expecting teammates, contributors, and his future self to accept the cost: lost readability and maintainability. All to satisfy that hot context for no concrete win once it goes cold.</p>
<p>Since the <code>Suspend</code> arm has grown beyond a handful of lines, someone proposes extracting it. You get a clean call site and the logic in a named function:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">handle_event</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">event</span>: <span class="nc">Event</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">match</span><span class="w"> </span><span class="n">event</span><span class="p">.</span><span class="n">kind</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">EventKind</span>::<span class="n">Suspend</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">handle_suspend</span><span class="p">(</span><span class="n">event</span><span class="p">).</span><span class="k">await</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// … other arms …
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">handle_suspend</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">event</span>: <span class="nc">Event</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// … &gt; 20 lines of app behavior …
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Then someone on the team raises an eyebrow. &ldquo;Isn&rsquo;t that an extra function call? Indirection has a cost.&rdquo; Another member quickly nods.</p>
<p>They&rsquo;re not wrong in principle. But are they right in practice?</p>
<p>What&rsquo;s the compiler&rsquo;s opinion on this? Let&rsquo;s think through it carefully and <a href="https://play.rust-lang.org/?version=stable&amp;mode=release&amp;edition=2024&amp;gist=3b7686a5c8f4238d27c33009bdeac1d9">measure</a> it.</p>
<p>First, the technically correct abstract concern: extracting code into an async function means:</p>
<ul>
<li>Parameter passing and ABI compliance.</li>
<li>Runtime future state machine setup (e.g. pointing to a created state, similar to a jump table).</li>
<li>Pinning a self reference (needed when borrowing across awaits).</li>
<li>Pushing and popping a stack frame — with adequate pointer nailing.</li>
<li>Extra runtime indirections for scheduling queues, wakers, and the event loop.</li>
</ul>
<p>These are all things that <em>exist</em>. The question is whether they matter relative to everything else going on.</p>
<p>In our snippet, the <code>Suspend</code> arm does what <code>handle_suspend</code> does; the <code>match</code> is already a branch. A <code>match</code> on an enum compiles to a jump table or a chain of comparisons. You&rsquo;ve already paid for a branch. Adding a function call inside one arm adds roughly one more indirect jump and frame setup — that&rsquo;s noise compared to whatever actual work <code>Suspend</code> is supposed to do.</p>
<p>When you <code>await</code> an async function, the compiler doesn&rsquo;t necessarily allocate a new object on the heap or introduce dynamic dispatch (you&rsquo;d rarely <code>Box</code> the future). What happens is that it merges the callee&rsquo;s state into the parent <code>Future</code>&rsquo;s state machine. That can make the abstraction genuinely free: your async future gets flattened into the callee&rsquo;s state machine. The extra <code>await</code> point you&rsquo;re worried about may compile down to the same state transitions the inline version would have produced anyway.</p>
<p>Are you letting the wrong details dominate code design?</p>
<p>What you are actually trying to get your system to achieve is what influences execution the most. If the <code>Suspend</code> path involves I/O, a lock, or any kind of allocation, you&rsquo;re measuring nanosecond noise against microsecond signal. A function call boundary is on the order of a few cycles. A syscall is thousands. Don&rsquo;t treat them as the same kind of cost. You know these don&rsquo;t belong in the same conversation.</p>
<p>In release mode, with optimizations enabled, the compiler will often <strong>inline</strong> small extracted functions automatically. The two versions — inline and extracted — can produce <em>identical assembly</em>.</p>
<p>You can <a href="https://github.com/sebastianconcept/indirection_bench">check this</a> yourself.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="sd">/// Work done entirely inside this function (no extra call).
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">do_the_work_inlined</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="kt">u64</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">acc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">u64</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">1</span><span class="o">..=</span><span class="mi">10</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">acc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">acc</span><span class="p">.</span><span class="n">wrapping_mul</span><span class="p">(</span><span class="n">i</span><span class="p">).</span><span class="n">wrapping_add</span><span class="p">(</span><span class="mi">12345</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">acc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="sd">/// Same work, but behind an extracted helper (one extra function call).
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">do_the_work_extracted</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="kt">u64</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">do_some_work</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">do_some_work</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="kt">u64</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">acc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">u64</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">1</span><span class="o">..=</span><span class="mi">10</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">acc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">acc</span><span class="p">.</span><span class="n">wrapping_mul</span><span class="p">(</span><span class="n">i</span><span class="p">).</span><span class="n">wrapping_add</span><span class="p">(</span><span class="mi">12345</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">acc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Emit assembly locally:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cargo rustc --release -- --emit asm
</span></span></code></pre></div><p>And look at the output (it will be an asm <code>.s</code> file in <code>target/release/deps</code>). If the assembly of <code>do_the_work_inlined</code> and <code>do_the_work_extracted</code> is the same, the debate is over before it started.</p>
<p>Folks are arguing about a distinction that doesn&rsquo;t survive compilation.</p>
<p>Now the thesis is clear: Indirection is not the enemy of performance — it’s a <em>misconception</em> that makes engineers chase invisible gains.</p>
<p>For completeness let&rsquo;s acquire a sense of dimension and see what numbers we get <a href="https://github.com/sebastianconcept/indirection_bench">experimentally</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cargo bench
</span></span></code></pre></div><p><a href="https://crates.io/crates/criterion">Criterion</a> will give you mean execution times and confidence intervals. If the error bars overlap, the difference is not real and the team would be letting their discussion be dominated by noise.</p>
<p>If you need to take a performance regression seriously in that situation, we need to remember that microbenchmarks can mislead. For a more honest picture, run your actual application under a profiler:</p>
<ul>
<li><code>valgrind --tool=callgrind</code></li>
<li><code>perf record</code> or <code>perf report</code>, or flamegraph</li>
<li><code>dtrace</code> or Instruments (Time Profiler) on macOS</li>
</ul>
<p>Look at where time is actually being spent. If <code>handle_suspend</code> doesn&rsquo;t appear as a hotspot — or appears but at a fraction of a percent — the conversation is over.</p>
<p>When indirection actually matters?</p>
<p>There are cases where call overhead is worth thinking about:</p>
<ul>
<li><strong>Tight inner loops</strong> — a function called millions of times per second in a tight loop, where cache pressure and branch prediction genuinely matter.</li>
<li><strong><code>dyn Trait</code></strong> — dynamic dispatch is a real indirection: a vtable lookup that the compiler cannot inline through.</li>
<li><strong>Explicit indirection in performance-critical paths</strong> — same idea: the compiler loses visibility and can&rsquo;t optimize across the boundary.</li>
</ul>
<p>All cases where you are in a CPU intensive blocking task that, if you&rsquo;re not careful, could starve all the others.</p>
<p>But none of these apply to a <code>match</code> arm in an async function that handles discrete events. If your <code>Suspend</code> arm ran ten million times per second in a tight loop, you&rsquo;d have other things to worry about first.</p>
<p>Be honest: are you really building a systems-level program where micro-optimizations have weight, or an application where system-level behavior dominates? <a href="https://blog.sebastiansastre.co/posts/stable-problems">Sometimes the problem we&rsquo;re optimizing for isn&rsquo;t even stable yet</a>.</p>
<p>Then what about the cost that isn&rsquo;t paid at runtime?</p>
<p>That the measurement doesn&rsquo;t show up in a profiler doesn&rsquo;t mean it&rsquo;s any less real. These human-centric costs compound over time and outweigh wildly a few nanoseconds of execution.</p>
<p>Every developer who opens that function pays a comprehension tax.</p>
<p>Cognitive load isn&rsquo;t free. Every code review takes longer. Every future change carries a higher risk of introducing a bug because the context is harder to hold in your head. These costs compound over months in a way that a few nanoseconds of function call overhead never will.</p>
<p>And what about the next lines of code that will be added there? Will it make understanding the rules and system behavior ramifications easier or harder in that <code>Suspend</code> arm?</p>
<p>Rust&rsquo;s design philosophy is explicit about this: write clean abstractions, trust the optimizer, and reach for <code>#[inline]</code> or <code>#[inline(always)]</code> only when you have measured a real problem and have no better option.</p>
<p>This is what good engineering is about.</p>
<p>In practice, the extra call cost is <strong>zero in release builds</strong> and most times statistically insignificant. The <em>real</em> cost of removing indirection is the lost clarity, testability, and developer productivity that you pay for a handful of nanoseconds justified with transient personal convenience.</p>
<p>So next time someone asks whether to inline or extract, answer with the numbers from your release benchmark and remind them that <strong>performance wins are measured in seconds of I/O, not in a single call</strong>. Keep your code readable; let the compiler handle the rest.</p>
<p>Besides, is your system reaction to that event even unit testable? How do you test it clearly without one method that encapsulates that behavior?</p>
<p>Extract the darn function. Name it well. Provide meaning to it. You do that and now you have a place to add a comment about the <a href="https://blog.sebastiansastre.co/posts/explain-your-rules">rules</a> that apply when your system needs to react to that case. The most valuable comments are those that help you understand system behavior fast.</p>
<p>And if you&rsquo;re worried, then measure; if a profiler ever tells you that a specific call is a bottleneck, you&rsquo;ll have the data to justify the tradeoff. Until then, optimize for the people reading the code. <a href="https://blog.sebastiansastre.co/posts/process-before-solution">That includes you when you need to own how it works</a>. And by the way, the AI agents will benefit from that as much or even more.</p>
<p>Maintainability and understandability only show up when you&rsquo;re deliberate about them. Extracting meaning into well-named functions is how you practice that. Code aesthetics are a feature and they affect team and agentic coding performance, just not the kind you measure in the runtime.</p>
<p>And be warned: some will resist this and surrender to the convenience of their current mental context, betting they&rsquo;ll &ldquo;remember&rdquo; how they did it. Time will make that bet age badly. It&rsquo;s 2026 — other AI agents are already in execution loops, disciplined to code better than that.</p>
<p>And when executing coding becomes radically cheap, <a href="https://blog.sebastiansastre.co/posts/design-is-the-new-programming">how do you think your system behavior is driven</a>? What&rsquo;s the weight of understanding system behavior quickly in this new coding economy?</p>
<p>So I don&rsquo;t know you, but you&rsquo;ll never see me sacrificing meaning on the altar of &ldquo;winning one less indirection.&rdquo;</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/cost-of-indirection-rust.jpg" alt=""" loading="lazy" /></figure><p>We&rsquo;ve all heard the warning: &ldquo;Every extra function call adds overhead. Inline it!&rdquo; In Rust async code, that worry is almost always false.</p>
<p>Look at the size of that arm:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">handle_event</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">event</span>: <span class="nc">Event</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">match</span><span class="w"> </span><span class="n">event</span><span class="p">.</span><span class="n">kind</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">EventKind</span>::<span class="n">Suspend</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// … &gt; 20 lines of app behavior … 
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// … other arms …
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>The author had hot context in his head when he wrote it and now you&rsquo;ll see his bias to justify it. And make others justify it too — expecting teammates, contributors, and his future self to accept the cost: lost readability and maintainability. All to satisfy that hot context for no concrete win once it goes cold.</p>
<p>Since the <code>Suspend</code> arm has grown beyond a handful of lines, someone proposes extracting it. You get a clean call site and the logic in a named function:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">handle_event</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">event</span>: <span class="nc">Event</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">match</span><span class="w"> </span><span class="n">event</span><span class="p">.</span><span class="n">kind</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">EventKind</span>::<span class="n">Suspend</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">handle_suspend</span><span class="p">(</span><span class="n">event</span><span class="p">).</span><span class="k">await</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// … other arms …
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">handle_suspend</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">event</span>: <span class="nc">Event</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// … &gt; 20 lines of app behavior …
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Then someone on the team raises an eyebrow. &ldquo;Isn&rsquo;t that an extra function call? Indirection has a cost.&rdquo; Another member quickly nods.</p>
<p>They&rsquo;re not wrong in principle. But are they right in practice?</p>
<p>What&rsquo;s the compiler&rsquo;s opinion on this? Let&rsquo;s think through it carefully and <a href="https://play.rust-lang.org/?version=stable&amp;mode=release&amp;edition=2024&amp;gist=3b7686a5c8f4238d27c33009bdeac1d9">measure</a> it.</p>
<p>First, the technically correct abstract concern: extracting code into an async function means:</p>
<ul>
<li>Parameter passing and ABI compliance.</li>
<li>Runtime future state machine setup (e.g. pointing to a created state, similar to a jump table).</li>
<li>Pinning a self reference (needed when borrowing across awaits).</li>
<li>Pushing and popping a stack frame — with adequate pointer nailing.</li>
<li>Extra runtime indirections for scheduling queues, wakers, and the event loop.</li>
</ul>
<p>These are all things that <em>exist</em>. The question is whether they matter relative to everything else going on.</p>
<p>In our snippet, the <code>Suspend</code> arm does what <code>handle_suspend</code> does; the <code>match</code> is already a branch. A <code>match</code> on an enum compiles to a jump table or a chain of comparisons. You&rsquo;ve already paid for a branch. Adding a function call inside one arm adds roughly one more indirect jump and frame setup — that&rsquo;s noise compared to whatever actual work <code>Suspend</code> is supposed to do.</p>
<p>When you <code>await</code> an async function, the compiler doesn&rsquo;t necessarily allocate a new object on the heap or introduce dynamic dispatch (you&rsquo;d rarely <code>Box</code> the future). What happens is that it merges the callee&rsquo;s state into the parent <code>Future</code>&rsquo;s state machine. That can make the abstraction genuinely free: your async future gets flattened into the callee&rsquo;s state machine. The extra <code>await</code> point you&rsquo;re worried about may compile down to the same state transitions the inline version would have produced anyway.</p>
<p>Are you letting the wrong details dominate code design?</p>
<p>What you are actually trying to get your system to achieve is what influences execution the most. If the <code>Suspend</code> path involves I/O, a lock, or any kind of allocation, you&rsquo;re measuring nanosecond noise against microsecond signal. A function call boundary is on the order of a few cycles. A syscall is thousands. Don&rsquo;t treat them as the same kind of cost. You know these don&rsquo;t belong in the same conversation.</p>
<p>In release mode, with optimizations enabled, the compiler will often <strong>inline</strong> small extracted functions automatically. The two versions — inline and extracted — can produce <em>identical assembly</em>.</p>
<p>You can <a href="https://github.com/sebastianconcept/indirection_bench">check this</a> yourself.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="sd">/// Work done entirely inside this function (no extra call).
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">do_the_work_inlined</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="kt">u64</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">acc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">u64</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">1</span><span class="o">..=</span><span class="mi">10</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">acc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">acc</span><span class="p">.</span><span class="n">wrapping_mul</span><span class="p">(</span><span class="n">i</span><span class="p">).</span><span class="n">wrapping_add</span><span class="p">(</span><span class="mi">12345</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">acc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="sd">/// Same work, but behind an extracted helper (one extra function call).
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">do_the_work_extracted</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="kt">u64</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">do_some_work</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">do_some_work</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="kt">u64</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">acc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">u64</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">1</span><span class="o">..=</span><span class="mi">10</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">acc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">acc</span><span class="p">.</span><span class="n">wrapping_mul</span><span class="p">(</span><span class="n">i</span><span class="p">).</span><span class="n">wrapping_add</span><span class="p">(</span><span class="mi">12345</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">acc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Emit assembly locally:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cargo rustc --release -- --emit asm
</span></span></code></pre></div><p>And look at the output (it will be an asm <code>.s</code> file in <code>target/release/deps</code>). If the assembly of <code>do_the_work_inlined</code> and <code>do_the_work_extracted</code> is the same, the debate is over before it started.</p>
<p>Folks are arguing about a distinction that doesn&rsquo;t survive compilation.</p>
<p>Now the thesis is clear: Indirection is not the enemy of performance — it’s a <em>misconception</em> that makes engineers chase invisible gains.</p>
<p>For completeness let&rsquo;s acquire a sense of dimension and see what numbers we get <a href="https://github.com/sebastianconcept/indirection_bench">experimentally</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cargo bench
</span></span></code></pre></div><p><a href="https://crates.io/crates/criterion">Criterion</a> will give you mean execution times and confidence intervals. If the error bars overlap, the difference is not real and the team would be letting their discussion be dominated by noise.</p>
<p>If you need to take a performance regression seriously in that situation, we need to remember that microbenchmarks can mislead. For a more honest picture, run your actual application under a profiler:</p>
<ul>
<li><code>valgrind --tool=callgrind</code></li>
<li><code>perf record</code> or <code>perf report</code>, or flamegraph</li>
<li><code>dtrace</code> or Instruments (Time Profiler) on macOS</li>
</ul>
<p>Look at where time is actually being spent. If <code>handle_suspend</code> doesn&rsquo;t appear as a hotspot — or appears but at a fraction of a percent — the conversation is over.</p>
<p>When indirection actually matters?</p>
<p>There are cases where call overhead is worth thinking about:</p>
<ul>
<li><strong>Tight inner loops</strong> — a function called millions of times per second in a tight loop, where cache pressure and branch prediction genuinely matter.</li>
<li><strong><code>dyn Trait</code></strong> — dynamic dispatch is a real indirection: a vtable lookup that the compiler cannot inline through.</li>
<li><strong>Explicit indirection in performance-critical paths</strong> — same idea: the compiler loses visibility and can&rsquo;t optimize across the boundary.</li>
</ul>
<p>All cases where you are in a CPU intensive blocking task that, if you&rsquo;re not careful, could starve all the others.</p>
<p>But none of these apply to a <code>match</code> arm in an async function that handles discrete events. If your <code>Suspend</code> arm ran ten million times per second in a tight loop, you&rsquo;d have other things to worry about first.</p>
<p>Be honest: are you really building a systems-level program where micro-optimizations have weight, or an application where system-level behavior dominates? <a href="https://blog.sebastiansastre.co/posts/stable-problems">Sometimes the problem we&rsquo;re optimizing for isn&rsquo;t even stable yet</a>.</p>
<p>Then what about the cost that isn&rsquo;t paid at runtime?</p>
<p>That the measurement doesn&rsquo;t show up in a profiler doesn&rsquo;t mean it&rsquo;s any less real. These human-centric costs compound over time and outweigh wildly a few nanoseconds of execution.</p>
<p>Every developer who opens that function pays a comprehension tax.</p>
<p>Cognitive load isn&rsquo;t free. Every code review takes longer. Every future change carries a higher risk of introducing a bug because the context is harder to hold in your head. These costs compound over months in a way that a few nanoseconds of function call overhead never will.</p>
<p>And what about the next lines of code that will be added there? Will it make understanding the rules and system behavior ramifications easier or harder in that <code>Suspend</code> arm?</p>
<p>Rust&rsquo;s design philosophy is explicit about this: write clean abstractions, trust the optimizer, and reach for <code>#[inline]</code> or <code>#[inline(always)]</code> only when you have measured a real problem and have no better option.</p>
<p>This is what good engineering is about.</p>
<p>In practice, the extra call cost is <strong>zero in release builds</strong> and most times statistically insignificant. The <em>real</em> cost of removing indirection is the lost clarity, testability, and developer productivity that you pay for a handful of nanoseconds justified with transient personal convenience.</p>
<p>So next time someone asks whether to inline or extract, answer with the numbers from your release benchmark and remind them that <strong>performance wins are measured in seconds of I/O, not in a single call</strong>. Keep your code readable; let the compiler handle the rest.</p>
<p>Besides, is your system reaction to that event even unit testable? How do you test it clearly without one method that encapsulates that behavior?</p>
<p>Extract the darn function. Name it well. Provide meaning to it. You do that and now you have a place to add a comment about the <a href="https://blog.sebastiansastre.co/posts/explain-your-rules">rules</a> that apply when your system needs to react to that case. The most valuable comments are those that help you understand system behavior fast.</p>
<p>And if you&rsquo;re worried, then measure; if a profiler ever tells you that a specific call is a bottleneck, you&rsquo;ll have the data to justify the tradeoff. Until then, optimize for the people reading the code. <a href="https://blog.sebastiansastre.co/posts/process-before-solution">That includes you when you need to own how it works</a>. And by the way, the AI agents will benefit from that as much or even more.</p>
<p>Maintainability and understandability only show up when you&rsquo;re deliberate about them. Extracting meaning into well-named functions is how you practice that. Code aesthetics are a feature and they affect team and agentic coding performance, just not the kind you measure in the runtime.</p>
<p>And be warned: some will resist this and surrender to the convenience of their current mental context, betting they&rsquo;ll &ldquo;remember&rdquo; how they did it. Time will make that bet age badly. It&rsquo;s 2026 — other AI agents are already in execution loops, disciplined to code better than that.</p>
<p>And when executing coding becomes radically cheap, <a href="https://blog.sebastiansastre.co/posts/design-is-the-new-programming">how do you think your system behavior is driven</a>? What&rsquo;s the weight of understanding system behavior quickly in this new coding economy?</p>
<p>So I don&rsquo;t know you, but you&rsquo;ll never see me sacrificing meaning on the altar of &ldquo;winning one less indirection.&rdquo;</p>
]]></content:encoded>
    </item>
    <item>
      <title>Stable Problems</title>
      <link>https://blog.sebastiansastre.co/posts/stable-problems/</link>
      <pubDate>Thu, 05 Mar 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/stable-problems/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/stable-problems.jpg" type="image/jpeg" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/stable-problems.jpg" alt=""" loading="lazy" /></figure><p>You know the moment: someone writes the problem on the board. Someone else refines a word. A third adds a constraint. Then — silence and nods. The problem is defined.You can build from here. It feels solid.</p>
<p>Protecting that feeling and keeping it unchallenged is the trap.</p>
<p>We mistake agreement for stability.</p>
<p>A shared problem statement feels like progress because it gives the room something to hold. Long-term bets need an anchor — so we lock the problem in and call it stable. But we didn&rsquo;t stress-test it.</p>
<p>We didn&rsquo;t wait for the second day, the forgotten stakeholder, the edge case that reframes everything.</p>
<p>Our intellectual surface didn&rsquo;t expand enough; we forgot to listen to other voices soon enough, and their perceptions didn&rsquo;t have a chance to shape our design.</p>
<p>The problem we agreed on was the first draft.</p>
<p>We treated it like the final one.</p>
<p>You like to code — maybe too much — and it distracts you from the true intent: the problem that&rsquo;s trying to be understood before you build over it. How strong are you to refrain from jumping into coding too soon? Are you protected from yourself?</p>
<p>Why does that happen? Three things. We <strong>confirm</strong> what we already believe and ignore what doesn&rsquo;t fit. We <strong>freeze</strong> on the first clear formulation — the one that seemed obvious in hour one — and miss the messier layers underneath. And we <strong>overtrust</strong> the neat statement: it feels like work done, so we stop questioning it.</p>
<p>The result is architecture that fits the story we told ourselves in that meeting, not the story the world will tell us later. When new information arrives — and it always does — the design cracks. Not because the solution was wrong, but because it correctly solved the <a href="https://blog.sebastiansastre.co/posts/model-the-reality-not-the-patterns">wrong problem</a>.</p>
<p>It wasn&rsquo;t stable enough.</p>
<p>Don&rsquo;t start building when everyone nods. Start when the problem has survived a deliberate attack. Pause after the first formulation. Ask: <em>What would break this? Who isn&rsquo;t in the room? What do we assume about the environment that might change? How does it honour history? — backward compatibility. How is it friendly to the future? — flexible for behavioural extensibility.</em></p>
<p>Give yourself a chance to <a href="https://blog.sebastiansastre.co/posts/process-before-solution">design how the problem is seen</a>.</p>
<p>You don&rsquo;t need scope creep to avoid falling into <a href="https://blog.sebastiansastre.co/posts/the-relative-trap">the relative trap</a>.</p>
<p>Revisit the problem after each round of thinking — peel layers until what remains has actually been challenged. When the problem is properly bounded, the rules of your design become <a href="https://blog.sebastiansastre.co/posts/explain-your-rules">explainable</a>.</p>
<p>Feed it with real input: second-day insights, colleagues who see different angles, usage and constraints that show up only later. Treat the problem as a living thing that gets revised, not a box you close on day one. True stability isn&rsquo;t the absence of doubt. It&rsquo;s what&rsquo;s left after doubt has done its work.</p>
<p>When the problem itself is still unstable, precision is theatre. The discipline is simple: keep questioning. Stay open to new input. Only commit when the problem gives you reality today, good history, and a design friendly to new rules tomorrow. When <a href="https://blog.sebastiansastre.co/posts/design-is-the-new-programming">design is the new programming</a>, that discipline is where leverage lives.</p>
<p>The best problem statement in the room is not the one everyone agreed on first. It&rsquo;s the one that generated the solution that survives when the real world tries to punch it in the face.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/stable-problems.jpg" alt=""" loading="lazy" /></figure><p>You know the moment: someone writes the problem on the board. Someone else refines a word. A third adds a constraint. Then — silence and nods. The problem is defined.You can build from here. It feels solid.</p>
<p>Protecting that feeling and keeping it unchallenged is the trap.</p>
<p>We mistake agreement for stability.</p>
<p>A shared problem statement feels like progress because it gives the room something to hold. Long-term bets need an anchor — so we lock the problem in and call it stable. But we didn&rsquo;t stress-test it.</p>
<p>We didn&rsquo;t wait for the second day, the forgotten stakeholder, the edge case that reframes everything.</p>
<p>Our intellectual surface didn&rsquo;t expand enough; we forgot to listen to other voices soon enough, and their perceptions didn&rsquo;t have a chance to shape our design.</p>
<p>The problem we agreed on was the first draft.</p>
<p>We treated it like the final one.</p>
<p>You like to code — maybe too much — and it distracts you from the true intent: the problem that&rsquo;s trying to be understood before you build over it. How strong are you to refrain from jumping into coding too soon? Are you protected from yourself?</p>
<p>Why does that happen? Three things. We <strong>confirm</strong> what we already believe and ignore what doesn&rsquo;t fit. We <strong>freeze</strong> on the first clear formulation — the one that seemed obvious in hour one — and miss the messier layers underneath. And we <strong>overtrust</strong> the neat statement: it feels like work done, so we stop questioning it.</p>
<p>The result is architecture that fits the story we told ourselves in that meeting, not the story the world will tell us later. When new information arrives — and it always does — the design cracks. Not because the solution was wrong, but because it correctly solved the <a href="https://blog.sebastiansastre.co/posts/model-the-reality-not-the-patterns">wrong problem</a>.</p>
<p>It wasn&rsquo;t stable enough.</p>
<p>Don&rsquo;t start building when everyone nods. Start when the problem has survived a deliberate attack. Pause after the first formulation. Ask: <em>What would break this? Who isn&rsquo;t in the room? What do we assume about the environment that might change? How does it honour history? — backward compatibility. How is it friendly to the future? — flexible for behavioural extensibility.</em></p>
<p>Give yourself a chance to <a href="https://blog.sebastiansastre.co/posts/process-before-solution">design how the problem is seen</a>.</p>
<p>You don&rsquo;t need scope creep to avoid falling into <a href="https://blog.sebastiansastre.co/posts/the-relative-trap">the relative trap</a>.</p>
<p>Revisit the problem after each round of thinking — peel layers until what remains has actually been challenged. When the problem is properly bounded, the rules of your design become <a href="https://blog.sebastiansastre.co/posts/explain-your-rules">explainable</a>.</p>
<p>Feed it with real input: second-day insights, colleagues who see different angles, usage and constraints that show up only later. Treat the problem as a living thing that gets revised, not a box you close on day one. True stability isn&rsquo;t the absence of doubt. It&rsquo;s what&rsquo;s left after doubt has done its work.</p>
<p>When the problem itself is still unstable, precision is theatre. The discipline is simple: keep questioning. Stay open to new input. Only commit when the problem gives you reality today, good history, and a design friendly to new rules tomorrow. When <a href="https://blog.sebastiansastre.co/posts/design-is-the-new-programming">design is the new programming</a>, that discipline is where leverage lives.</p>
<p>The best problem statement in the room is not the one everyone agreed on first. It&rsquo;s the one that generated the solution that survives when the real world tries to punch it in the face.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Why This Blog Uses RSS (Not a Newsletter)</title>
      <link>https://blog.sebastiansastre.co/posts/why-this-blog-uses-rss/</link>
      <pubDate>Thu, 05 Mar 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/why-this-blog-uses-rss/</guid>
      <description><![CDATA[<p>You could subscribe to this blog via a newsletter. I could run it on Substack, Beehiiv, or ConvertKit — capture emails, track opens, grow a list. That’s the default today: <em>pick a platform, collect subscribers, ship to their inbox.</em></p>
<p><strong>The platform then owns the relationship.</strong> Your readers are <em>their</em> users. Deliverability, design, and discovery depend on their rules and their algorithms. You’re building on someone else’s land. When the platform changes the product or the pricing, you adapt. When they decide what “engagement” means, you comply.</p>
<p>I’m not against newsletters. They work for many goals. But for <em>this</em> blog — a conceptual stream, not a growth funnel — the goal is different. I want you to read the ideas in full, in a place you choose, without a middleman deciding what you see or how.</p>
<p>So the process comes first: <strong>choose the protocol, not the platform.</strong></p>
<p>RSS is a protocol. It doesn’t have a dashboard, an algorithm, or a business model that depends on your attention. It just says: here’s the feed URL, here’s the content. You pick the reader — NetNewsWire, Feedly, something else — and you get the full text. No “click to read more,” no tracking pixels, no platform lock-in. You read offline if you want. You keep the archive in your own app. The content is the same whether you’re on an iPhone, a laptop, or a terminal.</p>
<p>That’s the sovereign reading experience.</p>
<p>Before adding a tool (newsletter software, analytics, paywalls), I fixed the constraint. <strong>Neutral protocol. Full content. Reader’s choice.</strong> Everything else — how many people subscribe, how they found the feed, what device they use — is secondary. It&rsquo;s fine to add downstream distribution channels later (a newsletter mirror, social); this feed is the source of truth. The theses are here, in full, and you can follow them in a way that doesn’t depend on a single company’s good behavior.</p>
<p>If you want to subscribe, the feed is one URL: <strong><a href="https://blog.sebastiansastre.co/index.xml">https://blog.sebastiansastre.co/index.xml</a></strong>. You can paste it into any RSS reader. I also have a short <a href="https://blog.sebastiansastre.co/subscribe/">Subscribe</a> page that explains what RSS is and suggests a couple of readers if you’ve never used one.</p>
<p>No account on this site. No email capture. Just the feed and the content. That&rsquo;s the design.</p>
]]></description>
      <content:encoded><![CDATA[<p>You could subscribe to this blog via a newsletter. I could run it on Substack, Beehiiv, or ConvertKit — capture emails, track opens, grow a list. That’s the default today: <em>pick a platform, collect subscribers, ship to their inbox.</em></p>
<p><strong>The platform then owns the relationship.</strong> Your readers are <em>their</em> users. Deliverability, design, and discovery depend on their rules and their algorithms. You’re building on someone else’s land. When the platform changes the product or the pricing, you adapt. When they decide what “engagement” means, you comply.</p>
<p>I’m not against newsletters. They work for many goals. But for <em>this</em> blog — a conceptual stream, not a growth funnel — the goal is different. I want you to read the ideas in full, in a place you choose, without a middleman deciding what you see or how.</p>
<p>So the process comes first: <strong>choose the protocol, not the platform.</strong></p>
<p>RSS is a protocol. It doesn’t have a dashboard, an algorithm, or a business model that depends on your attention. It just says: here’s the feed URL, here’s the content. You pick the reader — NetNewsWire, Feedly, something else — and you get the full text. No “click to read more,” no tracking pixels, no platform lock-in. You read offline if you want. You keep the archive in your own app. The content is the same whether you’re on an iPhone, a laptop, or a terminal.</p>
<p>That’s the sovereign reading experience.</p>
<p>Before adding a tool (newsletter software, analytics, paywalls), I fixed the constraint. <strong>Neutral protocol. Full content. Reader’s choice.</strong> Everything else — how many people subscribe, how they found the feed, what device they use — is secondary. It&rsquo;s fine to add downstream distribution channels later (a newsletter mirror, social); this feed is the source of truth. The theses are here, in full, and you can follow them in a way that doesn’t depend on a single company’s good behavior.</p>
<p>If you want to subscribe, the feed is one URL: <strong><a href="https://blog.sebastiansastre.co/index.xml">https://blog.sebastiansastre.co/index.xml</a></strong>. You can paste it into any RSS reader. I also have a short <a href="https://blog.sebastiansastre.co/subscribe/">Subscribe</a> page that explains what RSS is and suggests a couple of readers if you’ve never used one.</p>
<p>No account on this site. No email capture. Just the feed and the content. That&rsquo;s the design.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Where Is Your Manifesto?</title>
      <link>https://blog.sebastiansastre.co/posts/where-is-your-manifesto/</link>
      <pubDate>Fri, 27 Feb 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/where-is-your-manifesto/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/where-is-your-manifesto.jpg" type="image/jpeg" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/where-is-your-manifesto.jpg" alt=""" loading="lazy" /></figure><p>Most companies have a mission. Almost none have a manifesto. Branding is not enough.</p>
<p>A mission tells people what you do. It&rsquo;s operational — &ldquo;We build X,&rdquo; &ldquo;We help Y achieve Z.&rdquo; Useful. 99% relevant to contributors and team leaders, and 1% relevant to the curious.</p>
<p>But what about the public?</p>
<p>For them a mission is easy to forget because your mission is not <a href="https://blog.sebastiansastre.co/posts/the-accelerator-and-the-brake">their mission</a>.</p>
<p>A manifesto is different. It&rsquo;s a public promise: who you are, why you exist, and what you will and won&rsquo;t do while you&rsquo;re doing it. Most teams have the first. Few have the second. So when chaos hits — a tempting deal, a pushy client, a cost cut — there&rsquo;s no filter. Decisions follow mood, pressure, or the last person in the room. The mission doesn&rsquo;t help, because it never promised anything. It only described activity.</p>
<p>The mission is an operations guide. A manifesto is fuel.</p>
<p>The manifesto is <em>where are we going?</em> The mission is <em>how do we get there?</em> Different levels of attention.</p>
<p>The manifesto is poetic, emotional, the thing that makes you wake up with fire. The mission is practical, measurable, sometimes dull — the daily grind that fulfills the promise.</p>
<p>If you only have a manifesto, you dream and don&rsquo;t deliver. If you only have a mission, you operate like a soulless robot. When you have both and they&rsquo;re aligned, you have something rare: a business that knows what it stands for and actually does it.</p>
<p>This enables people paying attention to track your reputation beyond stock price or a balance sheet: they can track <a href="https://blog.sebastiansastre.co/posts/design-is-the-new-programming">your story</a> beyond your current offers.</p>
<p>&ldquo;It all begins with &hellip;&rdquo;</p>
<p>Inception happens when your promise <a href="https://blog.sebastiansastre.co/posts/you-cant-negotiate-genuine-desire">nurtures people&rsquo;s imagination</a>. They cannot articulate how to continue that story easily by themselves. The manifesto makes it easier for them to <a href="https://blog.sebastiansastre.co/posts/your-turn">talk about you</a>.</p>
<p>&ldquo;Promise&rdquo; is the lever. When you say out loud — to your team, your clients, the world — &ldquo;In this company we never sell crap disguised as premium,&rdquo; that sentence becomes <a href="https://blog.sebastiansastre.co/posts/explain-your-rules">a standard you can explain</a>.</p>
<p>People use it to reject projects, to defend values, to hire and fire. But also to justify why they &ldquo;hired&rdquo; your product. It becomes culture because it helps them make progress. A mission statement doesn&rsquo;t do that. It describes. A manifesto commits.</p>
<p>And that commitment creates accountability.</p>
<p>You can&rsquo;t hide behind &ldquo;we&rsquo;re a growth company&rdquo; when your manifesto says you don&rsquo;t sacrifice quality for scale. The promise is the contract.</p>
<p>But hold on, isn&rsquo;t that just branding?</p>
<p>No. Branding is how you want to be seen. The desire for an outcome. A manifesto is what you&rsquo;re willing to be held to.</p>
<p>It&rsquo;s a decision.</p>
<p>This decision functions as a filter — &ldquo;Does this fit what we promised?&rdquo; — and a moral contract. When money is tight or a shady opportunity appears, the manifesto is the compass.</p>
<p>Many businesses fail not because they lacked strategy, but because they never clearly defined their boundaries and their promises. The manifesto protects you from yourself. It also attracts the right clients and talent and repels the rest. People who don&rsquo;t fit leave on their own. That&rsquo;s not branding. That&rsquo;s structure.</p>
<p>Once you have clarity on when you&rsquo;re talking to your team of leaders and when you&rsquo;re talking to your clients, the question isn&rsquo;t &ldquo;What&rsquo;s your mission?&rdquo; The question is: What did you promise, and did you keep it?</p>
<p>If you can&rsquo;t answer the first part, you don&rsquo;t have a manifesto. Drafting an improvised one on the spot doesn&rsquo;t show respect for a long-term commitment. Write the promise. Then <a href="https://blog.sebastiansastre.co/posts/process-before-solution">build the mission to fulfill it</a>.</p>
<p>The manifesto is the fire. The mission is the engine.</p>
<p>Most companies only have the engine. No wonder so many of them run hot and go nowhere.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/where-is-your-manifesto.jpg" alt=""" loading="lazy" /></figure><p>Most companies have a mission. Almost none have a manifesto. Branding is not enough.</p>
<p>A mission tells people what you do. It&rsquo;s operational — &ldquo;We build X,&rdquo; &ldquo;We help Y achieve Z.&rdquo; Useful. 99% relevant to contributors and team leaders, and 1% relevant to the curious.</p>
<p>But what about the public?</p>
<p>For them a mission is easy to forget because your mission is not <a href="https://blog.sebastiansastre.co/posts/the-accelerator-and-the-brake">their mission</a>.</p>
<p>A manifesto is different. It&rsquo;s a public promise: who you are, why you exist, and what you will and won&rsquo;t do while you&rsquo;re doing it. Most teams have the first. Few have the second. So when chaos hits — a tempting deal, a pushy client, a cost cut — there&rsquo;s no filter. Decisions follow mood, pressure, or the last person in the room. The mission doesn&rsquo;t help, because it never promised anything. It only described activity.</p>
<p>The mission is an operations guide. A manifesto is fuel.</p>
<p>The manifesto is <em>where are we going?</em> The mission is <em>how do we get there?</em> Different levels of attention.</p>
<p>The manifesto is poetic, emotional, the thing that makes you wake up with fire. The mission is practical, measurable, sometimes dull — the daily grind that fulfills the promise.</p>
<p>If you only have a manifesto, you dream and don&rsquo;t deliver. If you only have a mission, you operate like a soulless robot. When you have both and they&rsquo;re aligned, you have something rare: a business that knows what it stands for and actually does it.</p>
<p>This enables people paying attention to track your reputation beyond stock price or a balance sheet: they can track <a href="https://blog.sebastiansastre.co/posts/design-is-the-new-programming">your story</a> beyond your current offers.</p>
<p>&ldquo;It all begins with &hellip;&rdquo;</p>
<p>Inception happens when your promise <a href="https://blog.sebastiansastre.co/posts/you-cant-negotiate-genuine-desire">nurtures people&rsquo;s imagination</a>. They cannot articulate how to continue that story easily by themselves. The manifesto makes it easier for them to <a href="https://blog.sebastiansastre.co/posts/your-turn">talk about you</a>.</p>
<p>&ldquo;Promise&rdquo; is the lever. When you say out loud — to your team, your clients, the world — &ldquo;In this company we never sell crap disguised as premium,&rdquo; that sentence becomes <a href="https://blog.sebastiansastre.co/posts/explain-your-rules">a standard you can explain</a>.</p>
<p>People use it to reject projects, to defend values, to hire and fire. But also to justify why they &ldquo;hired&rdquo; your product. It becomes culture because it helps them make progress. A mission statement doesn&rsquo;t do that. It describes. A manifesto commits.</p>
<p>And that commitment creates accountability.</p>
<p>You can&rsquo;t hide behind &ldquo;we&rsquo;re a growth company&rdquo; when your manifesto says you don&rsquo;t sacrifice quality for scale. The promise is the contract.</p>
<p>But hold on, isn&rsquo;t that just branding?</p>
<p>No. Branding is how you want to be seen. The desire for an outcome. A manifesto is what you&rsquo;re willing to be held to.</p>
<p>It&rsquo;s a decision.</p>
<p>This decision functions as a filter — &ldquo;Does this fit what we promised?&rdquo; — and a moral contract. When money is tight or a shady opportunity appears, the manifesto is the compass.</p>
<p>Many businesses fail not because they lacked strategy, but because they never clearly defined their boundaries and their promises. The manifesto protects you from yourself. It also attracts the right clients and talent and repels the rest. People who don&rsquo;t fit leave on their own. That&rsquo;s not branding. That&rsquo;s structure.</p>
<p>Once you have clarity on when you&rsquo;re talking to your team of leaders and when you&rsquo;re talking to your clients, the question isn&rsquo;t &ldquo;What&rsquo;s your mission?&rdquo; The question is: What did you promise, and did you keep it?</p>
<p>If you can&rsquo;t answer the first part, you don&rsquo;t have a manifesto. Drafting an improvised one on the spot doesn&rsquo;t show respect for a long-term commitment. Write the promise. Then <a href="https://blog.sebastiansastre.co/posts/process-before-solution">build the mission to fulfill it</a>.</p>
<p>The manifesto is the fire. The mission is the engine.</p>
<p>Most companies only have the engine. No wonder so many of them run hot and go nowhere.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Design Is the New Programming</title>
      <link>https://blog.sebastiansastre.co/posts/design-is-the-new-programming/</link>
      <pubDate>Thu, 26 Feb 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/design-is-the-new-programming/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/design-is-the-new-programming.webp" type="image/webp" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/design-is-the-new-programming.webp" alt=""" loading="lazy" /></figure><p>Fortran. Lisp. C. Smalltalk. Ruby. Rust.</p>
<p>Seventy years of increasingly powerful languages, each one turning the previous generation&rsquo;s hard problems into routine. Then, somewhere around 2025, the list took a turn nobody on it anticipated.</p>
<p>English.</p>
<p>Well, natural language.</p>
<p>Not a syntax. Not a virtual machine. Not a paradigm. Not even a formal language. Just what you&rsquo;d say to a colleague. AI transpiles your intent into source code so compilers can translate it to machine instructions. The exclusive discipline that required mastering at least one programming language? Over. The bottleneck that kept most people from building software? Dissolved.</p>
<p>An MVP, coded in a week. Senior-level architecture. Maybe not production-grade, but demo-ready — and from there <a href="https://blog.sebastiansastre.co/posts/your-turn">you can go places</a>. Any language you want. It&rsquo;s not fiction anymore.</p>
<p>So now what?</p>
<p>This isn&rsquo;t just happening to code.</p>
<p>Architecture is design for buildings. A craftsman making high-fashion shoes is a designer for footwear. A typographer designs reading experiences. A novelist designs characters and their stories.</p>
<p>The heavy lifting of execution is being disrupted <em>everywhere</em>. Not just in software. Across every discipline where &ldquo;making the thing&rdquo; used to be the hard part.</p>
<p>When the making gets cheap, what&rsquo;s left?</p>
<p>What&rsquo;s left is the filters that let you <em>express intent</em> when building something. Having the discernment of what to build and why.</p>
<p>What&rsquo;s left is design.</p>
<p>Not design as &ldquo;make it pretty.&rdquo; Design as the discipline of shaping how something works, how it&rsquo;s experienced, how it communicates intent before a single word is read or a single button is pressed.</p>
<p>But not just product shaping either. Design <a href="https://blog.sebastiansastre.co/posts/process-before-solution">as the process behind the product</a>. How the paths for its growth get their friction removed. When we think strategy, we are designing a route. Steering. Shaping a journey.</p>
<p>If execution is now 90% handled (or choose your number) the question isn&rsquo;t <em>&ldquo;can I build it?&rdquo;</em> It&rsquo;s <em>&ldquo;do I know what to build and why this shape?&rdquo;</em></p>
<p>How to answer that? With an evasive maneuver — a short-term win that saves the day — or a frontal, long-term adaptive answer?</p>
<p>Place your bet.</p>
<p>Here is one: Own it.</p>
<p>Be the factory owner. Own the attention that used to go to execution by owning the process of how your product is shaped, how it shapes its audience&rsquo;s experiences, and how it reaches them.</p>
<p>Fill it with what makes a remarkable product for its given people. Finding them means you found the commonalities, and detecting those patterns means you&rsquo;re sensitive to good design.</p>
<p>Here is where most technical people quietly fail.</p>
<p>Lack of proportion: not helping the user navigate their journey.</p>
<p>Poor contrast: you fail to communicate what&rsquo;s important the moment it sits next to something else.</p>
<p>Poor balance or missed alignments: you reveal kindergarten-level design thinking.</p>
<p><a href="https://blog.sebastiansastre.co/posts/explain-your-rules">Unclear hierarchy</a>: your proposals feel dumb or unintelligible.</p>
<p>Lack of unity: your entire promise is weak and insubstantial.</p>
<p>Disregarded patterns: you&rsquo;re just another one who shipped something &ldquo;almost good enough.&rdquo;</p>
<p>These aren&rsquo;t aesthetic preferences. They&rsquo;re structural failures. The same way a buffer overflow or a race condition is in code, except they live in the layer your users actually touch.</p>
<p>You can prompt an AI to produce senior-level internal software architecture. You cannot prompt your way into understanding why your product feels wrong to hold.</p>
<p>Every product has a designer who expressed intent when shaping it. Every user experiencing that shape receives a <a href="https://blog.sebastiansastre.co/posts/the-accelerator-and-the-brake">force of progress or of friction</a>. That symbolization process is happening whether you designed it deliberately or by neglect.</p>
<p>The factory is yours now. AI put the machines, the materials, and the labor in the hands of anyone willing to use them.</p>
<p>But a factory without someone who understands processes, proportion, contrast, balance, hierarchy, unity, and patterns isn&rsquo;t a factory. It&rsquo;s a warehouse full of parts nobody wants to assemble.</p>
<p>People notice fast when the job is incomplete.</p>
<p>Keep ignoring that you shape every level and you&rsquo;re removing yourself from the possibility of being a builder. You&rsquo;ll own the machines but not know what they should make — or for whom — or why anyone should care.</p>
<p>Once natural language is the new programming language, <strong>design</strong> is the new programming language.</p>
<p>Program well.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/design-is-the-new-programming.webp" alt=""" loading="lazy" /></figure><p>Fortran. Lisp. C. Smalltalk. Ruby. Rust.</p>
<p>Seventy years of increasingly powerful languages, each one turning the previous generation&rsquo;s hard problems into routine. Then, somewhere around 2025, the list took a turn nobody on it anticipated.</p>
<p>English.</p>
<p>Well, natural language.</p>
<p>Not a syntax. Not a virtual machine. Not a paradigm. Not even a formal language. Just what you&rsquo;d say to a colleague. AI transpiles your intent into source code so compilers can translate it to machine instructions. The exclusive discipline that required mastering at least one programming language? Over. The bottleneck that kept most people from building software? Dissolved.</p>
<p>An MVP, coded in a week. Senior-level architecture. Maybe not production-grade, but demo-ready — and from there <a href="https://blog.sebastiansastre.co/posts/your-turn">you can go places</a>. Any language you want. It&rsquo;s not fiction anymore.</p>
<p>So now what?</p>
<p>This isn&rsquo;t just happening to code.</p>
<p>Architecture is design for buildings. A craftsman making high-fashion shoes is a designer for footwear. A typographer designs reading experiences. A novelist designs characters and their stories.</p>
<p>The heavy lifting of execution is being disrupted <em>everywhere</em>. Not just in software. Across every discipline where &ldquo;making the thing&rdquo; used to be the hard part.</p>
<p>When the making gets cheap, what&rsquo;s left?</p>
<p>What&rsquo;s left is the filters that let you <em>express intent</em> when building something. Having the discernment of what to build and why.</p>
<p>What&rsquo;s left is design.</p>
<p>Not design as &ldquo;make it pretty.&rdquo; Design as the discipline of shaping how something works, how it&rsquo;s experienced, how it communicates intent before a single word is read or a single button is pressed.</p>
<p>But not just product shaping either. Design <a href="https://blog.sebastiansastre.co/posts/process-before-solution">as the process behind the product</a>. How the paths for its growth get their friction removed. When we think strategy, we are designing a route. Steering. Shaping a journey.</p>
<p>If execution is now 90% handled (or choose your number) the question isn&rsquo;t <em>&ldquo;can I build it?&rdquo;</em> It&rsquo;s <em>&ldquo;do I know what to build and why this shape?&rdquo;</em></p>
<p>How to answer that? With an evasive maneuver — a short-term win that saves the day — or a frontal, long-term adaptive answer?</p>
<p>Place your bet.</p>
<p>Here is one: Own it.</p>
<p>Be the factory owner. Own the attention that used to go to execution by owning the process of how your product is shaped, how it shapes its audience&rsquo;s experiences, and how it reaches them.</p>
<p>Fill it with what makes a remarkable product for its given people. Finding them means you found the commonalities, and detecting those patterns means you&rsquo;re sensitive to good design.</p>
<p>Here is where most technical people quietly fail.</p>
<p>Lack of proportion: not helping the user navigate their journey.</p>
<p>Poor contrast: you fail to communicate what&rsquo;s important the moment it sits next to something else.</p>
<p>Poor balance or missed alignments: you reveal kindergarten-level design thinking.</p>
<p><a href="https://blog.sebastiansastre.co/posts/explain-your-rules">Unclear hierarchy</a>: your proposals feel dumb or unintelligible.</p>
<p>Lack of unity: your entire promise is weak and insubstantial.</p>
<p>Disregarded patterns: you&rsquo;re just another one who shipped something &ldquo;almost good enough.&rdquo;</p>
<p>These aren&rsquo;t aesthetic preferences. They&rsquo;re structural failures. The same way a buffer overflow or a race condition is in code, except they live in the layer your users actually touch.</p>
<p>You can prompt an AI to produce senior-level internal software architecture. You cannot prompt your way into understanding why your product feels wrong to hold.</p>
<p>Every product has a designer who expressed intent when shaping it. Every user experiencing that shape receives a <a href="https://blog.sebastiansastre.co/posts/the-accelerator-and-the-brake">force of progress or of friction</a>. That symbolization process is happening whether you designed it deliberately or by neglect.</p>
<p>The factory is yours now. AI put the machines, the materials, and the labor in the hands of anyone willing to use them.</p>
<p>But a factory without someone who understands processes, proportion, contrast, balance, hierarchy, unity, and patterns isn&rsquo;t a factory. It&rsquo;s a warehouse full of parts nobody wants to assemble.</p>
<p>People notice fast when the job is incomplete.</p>
<p>Keep ignoring that you shape every level and you&rsquo;re removing yourself from the possibility of being a builder. You&rsquo;ll own the machines but not know what they should make — or for whom — or why anyone should care.</p>
<p>Once natural language is the new programming language, <strong>design</strong> is the new programming language.</p>
<p>Program well.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Process Before Solution</title>
      <link>https://blog.sebastiansastre.co/posts/process-before-solution/</link>
      <pubDate>Tue, 17 Feb 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/process-before-solution/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/factory-keys.webp" type="image/webp" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/factory-keys.webp" alt=""" loading="lazy" /></figure><p>The default today is simple: <em>AI is good. Prompt it. Ship the result.</em></p>
<p>It’s seductive. The model returns something usable — sometimes remarkable. You didn’t have to think hard about the problem. You didn’t have to break it down. You just asked and received. So you keep doing it. Prompt, accept, ship.</p>
<p>That’s not “using AI well.” That’s outsourcing control.</p>
<p>Here’s the uncomfortable part: the system got better at understanding and solving the problem, but <em>you</em> don’t know how.</p>
<p>You can’t walk through the layers. You can’t point to where the nuance was captured or where the devil was kept out. You can’t explain with precision how the solution ties back to the problem.</p>
<p>The intelligence improved; your grasp of it didn’t. You’re fine with all of that control living inside one platform — then what do you actually own?</p>
<p>The same thing can happen with a team. A business owner has clever people; they resolve the challenge and he doesn’t really know how.</p>
<p>The difference: he can have them walk through, examine and scrutinize each layer and how it fits the problem. They “own it”; they can teach him any detail. Together they are in control.</p>
<p>With AI? Not so much. You don’t get the walkthrough unless you intentionally build it.</p>
<p>Worst part? You can&rsquo;t tell how safe each layer really is. You can&rsquo;t be as confident — and that hurts your whole brand narrative. You can&rsquo;t be deterministic about outcomes. Clarity on the mission blurs. The promises in your manifesto weaken. So what&rsquo;s left for people to believe in? You&rsquo;re left with breadcrumbs and pretending. You want to sell something of substance or hype?</p>
<p><strong>Using AI well doesn’t mean just “get great output.” It means you own the process that produces the output.</strong></p>
<p>It means that you don&rsquo;t get passionate about your solutions so fast.</p>
<p>It means you love the problem more — and people in pain can find comfort in you. It means you are leveling up your maturity to select your creativity.</p>
<p>Your intention finds a channel of expression when you become <em>deliberate</em>.</p>
<p>When you put the process before solution <em>you</em> design how the problem is seen. <em>You</em> decompose it into smaller problems that can be fixed one at a time. <em>You</em> add the layers of decomposition that capture the nuances that would otherwise be the details where the devil gets in.</p>
<p>In the same way a business owner selects and trains his team members, you train or configure agents, add domain knowledge, and add feedback loops so the output is curated to your taste and your technical judgment and your vision of the world.</p>
<p>The alternative to “prompt and ship” isn’t “don’t use AI” or keep prompting more, making you a per-project micromanagement bottleneck.</p>
<p>The alternative is: own the curation of the process that leads to something remarkable. The heavy lifting isn&rsquo;t a problem anymore. You define the steps; the models collaborate to fill them.</p>
<p>You are the factory now.</p>
<p>You have the opportunity to use AI to <em>amplify you</em>. You know how the problem was framed, how it was broken down, and how the result was checked. You know how to scrutinize each quality gate and how your solution is architected.</p>
<p>That’s what you own.</p>
<p>Your process is either in control or you&rsquo;re a tenant in your own factory.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/factory-keys.webp" alt=""" loading="lazy" /></figure><p>The default today is simple: <em>AI is good. Prompt it. Ship the result.</em></p>
<p>It’s seductive. The model returns something usable — sometimes remarkable. You didn’t have to think hard about the problem. You didn’t have to break it down. You just asked and received. So you keep doing it. Prompt, accept, ship.</p>
<p>That’s not “using AI well.” That’s outsourcing control.</p>
<p>Here’s the uncomfortable part: the system got better at understanding and solving the problem, but <em>you</em> don’t know how.</p>
<p>You can’t walk through the layers. You can’t point to where the nuance was captured or where the devil was kept out. You can’t explain with precision how the solution ties back to the problem.</p>
<p>The intelligence improved; your grasp of it didn’t. You’re fine with all of that control living inside one platform — then what do you actually own?</p>
<p>The same thing can happen with a team. A business owner has clever people; they resolve the challenge and he doesn’t really know how.</p>
<p>The difference: he can have them walk through, examine and scrutinize each layer and how it fits the problem. They “own it”; they can teach him any detail. Together they are in control.</p>
<p>With AI? Not so much. You don’t get the walkthrough unless you intentionally build it.</p>
<p>Worst part? You can&rsquo;t tell how safe each layer really is. You can&rsquo;t be as confident — and that hurts your whole brand narrative. You can&rsquo;t be deterministic about outcomes. Clarity on the mission blurs. The promises in your manifesto weaken. So what&rsquo;s left for people to believe in? You&rsquo;re left with breadcrumbs and pretending. You want to sell something of substance or hype?</p>
<p><strong>Using AI well doesn’t mean just “get great output.” It means you own the process that produces the output.</strong></p>
<p>It means that you don&rsquo;t get passionate about your solutions so fast.</p>
<p>It means you love the problem more — and people in pain can find comfort in you. It means you are leveling up your maturity to select your creativity.</p>
<p>Your intention finds a channel of expression when you become <em>deliberate</em>.</p>
<p>When you put the process before solution <em>you</em> design how the problem is seen. <em>You</em> decompose it into smaller problems that can be fixed one at a time. <em>You</em> add the layers of decomposition that capture the nuances that would otherwise be the details where the devil gets in.</p>
<p>In the same way a business owner selects and trains his team members, you train or configure agents, add domain knowledge, and add feedback loops so the output is curated to your taste and your technical judgment and your vision of the world.</p>
<p>The alternative to “prompt and ship” isn’t “don’t use AI” or keep prompting more, making you a per-project micromanagement bottleneck.</p>
<p>The alternative is: own the curation of the process that leads to something remarkable. The heavy lifting isn&rsquo;t a problem anymore. You define the steps; the models collaborate to fill them.</p>
<p>You are the factory now.</p>
<p>You have the opportunity to use AI to <em>amplify you</em>. You know how the problem was framed, how it was broken down, and how the result was checked. You know how to scrutinize each quality gate and how your solution is architected.</p>
<p>That’s what you own.</p>
<p>Your process is either in control or you&rsquo;re a tenant in your own factory.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Your Turn</title>
      <link>https://blog.sebastiansastre.co/posts/your-turn/</link>
      <pubDate>Fri, 13 Feb 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/your-turn/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/your-turn.webp" type="image/webp" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/your-turn.webp" alt=""" loading="lazy" /></figure><p>You pushed the code. The tests are green. The feature is live.</p>
<p>And three days later, the audience finds out by accident.</p>
<p>This happens more often than anyone admits. Not because people don&rsquo;t care — but because creation and delivery run on fundamentally different fuels. And the tank is empty by the time delivery starts to matter.</p>
<h2 id="the-builders-trance">The builder&rsquo;s trance</h2>
<p>Development is a deep act of creation. You enter the loop: write, test, break, fix, refine. The happy path takes shape. Then the exceptions. Then the edge cases that only reveal themselves when you slow down and think in adversarial mode.</p>
<p>All looking good.</p>
<p>You&rsquo;re setting your workflow and your rules. Can you state them clearly? Do they hold under pressure? Under malformed state? Under the load nobody warned you about?</p>
<p>This phase demands total immersion. It rewards tunnel vision. The world outside the problem shrinks to nothing, and it <em>should</em> — because that focus is what makes the solution good. You are deep in your exciting technical journey, intermediary progress compounding, QA passing one scenario at a time.</p>
<p>And a good builder has good reason to stay there. That deep thinking is what lets you <a href="https://blog.sebastiansastre.co/posts/model-the-reality-not-the-patterns">model the reality, not the patterns</a> — to see the domain clearly enough to let the right abstractions emerge instead of forcing the wrong ones. It&rsquo;s also what keeps you from falling into <a href="https://blog.sebastiansastre.co/posts/the-relative-trap">the relative trap</a>, where everything becomes an infinite loop of interpretation keeping you in analysis paralysis. The trance isn&rsquo;t indulgence. It&rsquo;s how you cut through noise to reach the signal.</p>
<p>The builder&rsquo;s trance is sacred. It&rsquo;s how real things get made.</p>
<p>But that trance has a cost that too often is overlooked.</p>
<h2 id="three-games-three-fuels">Three games, three fuels</h2>
<p>Development demands <strong>creation focus</strong>. The iteration loop. Happy path, exception handling, rule enforcement. <em>Does this abstraction hold? Does that boundary leak? <a href="https://blog.sebastiansastre.co/posts/explain-your-rules">Can I explain why this works?</a></em> This is generative energy — the kind that builds things from nothing.</p>
<p>Release demands <strong>closure focus</strong>. A completely different animal. You&rsquo;re no longer asking <em>&ldquo;what should this do?&rdquo;</em> — you&rsquo;re asking <em>&ldquo;will this survive?&rdquo;</em> Can we play this new game confidently? In all circumstances? Under all expected loads? The rules you&rsquo;ve built need to be in full force, ready to become the new environment. Closure is not creativity — it&rsquo;s conviction. A different muscle, a different anxiety.</p>
<p>Landing demands a <strong>different focus entirely</strong>. Not <em>&ldquo;is it done?&rdquo;</em> but <em>&ldquo;how does the rest of the world find out — and yield this properly?&rdquo;</em> Whether it&rsquo;s a teammate, an organizer, or you switching hats — someone needs to orchestrate communication, set expectations, coordinate the rollout, make the feature matter to people who never saw the code. That someone is responsible for starting the story that makes this exciting for everyone else.</p>
<p>That story can&rsquo;t be told if nobody — including you — acknowledged the chapter is written.</p>
<h2 id="the-experts-blind-spot">The expert&rsquo;s blind spot</h2>
<p>Here is the brutal truth: the person who built the thing is the worst person to remember the handoff.</p>
<p>Not because they&rsquo;re careless. Because they&rsquo;re spent.</p>
<p>Creation is expensive. The developer has been context-switching between <em>&ldquo;how should this work&rdquo;</em> and <em>&ldquo;what happens when it doesn&rsquo;t&rdquo;</em> for days or weeks. When the last test passes, when the deploy succeeds, the brain doesn&rsquo;t celebrate — it collapses. Relief feels like completion. And a personal context refresh was well earned.</p>
<p>But deployed isn&rsquo;t delivered.</p>
<p>The deploy is the end of the building. Delivery is when someone — a teammate, your audience, or the next version of yourself — can carry it forward. That notification, the one that feels trivial, the one you&rsquo;ll <em>&ldquo;do in a minute&rdquo;</em> — is the bridge between a feature that exists and a feature that matters. Skip it, and your work sits in production like a gift nobody opened.</p>
<p>Cost of Delay and Time Value of Money weren&rsquo;t concepts created because of nothing. Ignore them and you&rsquo;ll be missing out on compounding returns on early capital that might never come back.</p>
<p>It is brutally easy for an expert to be exhausted at the finish line and forget that the finish line isn&rsquo;t theirs. It belongs to the person waiting on the other side.</p>
<hr>
<p>Creation, closure, and landing are three different games with three different rules. The mistake is treating them as one continuous flow because they feel sequential. They aren&rsquo;t. Each demands a different kind of attention, a different energy, a different definition of <em>&ldquo;done.&rdquo;</em></p>
<p>The expert&rsquo;s blind spot isn&rsquo;t incompetence. It&rsquo;s exhaustion masquerading as completion.</p>
<p>If you build something and don&rsquo;t hand it off, you didn&rsquo;t ship — you just deployed.</p>
<p>And deployed isn&rsquo;t delivered — so it can&rsquo;t lead.</p>
<p>The last act of building isn&rsquo;t the final commit. It&rsquo;s what lets the work lead somewhere.</p>
<p>It&rsquo;s the message that says: <em>&ldquo;It&rsquo;s ready. Your turn.&rdquo;</em></p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/your-turn.webp" alt=""" loading="lazy" /></figure><p>You pushed the code. The tests are green. The feature is live.</p>
<p>And three days later, the audience finds out by accident.</p>
<p>This happens more often than anyone admits. Not because people don&rsquo;t care — but because creation and delivery run on fundamentally different fuels. And the tank is empty by the time delivery starts to matter.</p>
<h2 id="the-builders-trance">The builder&rsquo;s trance</h2>
<p>Development is a deep act of creation. You enter the loop: write, test, break, fix, refine. The happy path takes shape. Then the exceptions. Then the edge cases that only reveal themselves when you slow down and think in adversarial mode.</p>
<p>All looking good.</p>
<p>You&rsquo;re setting your workflow and your rules. Can you state them clearly? Do they hold under pressure? Under malformed state? Under the load nobody warned you about?</p>
<p>This phase demands total immersion. It rewards tunnel vision. The world outside the problem shrinks to nothing, and it <em>should</em> — because that focus is what makes the solution good. You are deep in your exciting technical journey, intermediary progress compounding, QA passing one scenario at a time.</p>
<p>And a good builder has good reason to stay there. That deep thinking is what lets you <a href="https://blog.sebastiansastre.co/posts/model-the-reality-not-the-patterns">model the reality, not the patterns</a> — to see the domain clearly enough to let the right abstractions emerge instead of forcing the wrong ones. It&rsquo;s also what keeps you from falling into <a href="https://blog.sebastiansastre.co/posts/the-relative-trap">the relative trap</a>, where everything becomes an infinite loop of interpretation keeping you in analysis paralysis. The trance isn&rsquo;t indulgence. It&rsquo;s how you cut through noise to reach the signal.</p>
<p>The builder&rsquo;s trance is sacred. It&rsquo;s how real things get made.</p>
<p>But that trance has a cost that too often is overlooked.</p>
<h2 id="three-games-three-fuels">Three games, three fuels</h2>
<p>Development demands <strong>creation focus</strong>. The iteration loop. Happy path, exception handling, rule enforcement. <em>Does this abstraction hold? Does that boundary leak? <a href="https://blog.sebastiansastre.co/posts/explain-your-rules">Can I explain why this works?</a></em> This is generative energy — the kind that builds things from nothing.</p>
<p>Release demands <strong>closure focus</strong>. A completely different animal. You&rsquo;re no longer asking <em>&ldquo;what should this do?&rdquo;</em> — you&rsquo;re asking <em>&ldquo;will this survive?&rdquo;</em> Can we play this new game confidently? In all circumstances? Under all expected loads? The rules you&rsquo;ve built need to be in full force, ready to become the new environment. Closure is not creativity — it&rsquo;s conviction. A different muscle, a different anxiety.</p>
<p>Landing demands a <strong>different focus entirely</strong>. Not <em>&ldquo;is it done?&rdquo;</em> but <em>&ldquo;how does the rest of the world find out — and yield this properly?&rdquo;</em> Whether it&rsquo;s a teammate, an organizer, or you switching hats — someone needs to orchestrate communication, set expectations, coordinate the rollout, make the feature matter to people who never saw the code. That someone is responsible for starting the story that makes this exciting for everyone else.</p>
<p>That story can&rsquo;t be told if nobody — including you — acknowledged the chapter is written.</p>
<h2 id="the-experts-blind-spot">The expert&rsquo;s blind spot</h2>
<p>Here is the brutal truth: the person who built the thing is the worst person to remember the handoff.</p>
<p>Not because they&rsquo;re careless. Because they&rsquo;re spent.</p>
<p>Creation is expensive. The developer has been context-switching between <em>&ldquo;how should this work&rdquo;</em> and <em>&ldquo;what happens when it doesn&rsquo;t&rdquo;</em> for days or weeks. When the last test passes, when the deploy succeeds, the brain doesn&rsquo;t celebrate — it collapses. Relief feels like completion. And a personal context refresh was well earned.</p>
<p>But deployed isn&rsquo;t delivered.</p>
<p>The deploy is the end of the building. Delivery is when someone — a teammate, your audience, or the next version of yourself — can carry it forward. That notification, the one that feels trivial, the one you&rsquo;ll <em>&ldquo;do in a minute&rdquo;</em> — is the bridge between a feature that exists and a feature that matters. Skip it, and your work sits in production like a gift nobody opened.</p>
<p>Cost of Delay and Time Value of Money weren&rsquo;t concepts created because of nothing. Ignore them and you&rsquo;ll be missing out on compounding returns on early capital that might never come back.</p>
<p>It is brutally easy for an expert to be exhausted at the finish line and forget that the finish line isn&rsquo;t theirs. It belongs to the person waiting on the other side.</p>
<hr>
<p>Creation, closure, and landing are three different games with three different rules. The mistake is treating them as one continuous flow because they feel sequential. They aren&rsquo;t. Each demands a different kind of attention, a different energy, a different definition of <em>&ldquo;done.&rdquo;</em></p>
<p>The expert&rsquo;s blind spot isn&rsquo;t incompetence. It&rsquo;s exhaustion masquerading as completion.</p>
<p>If you build something and don&rsquo;t hand it off, you didn&rsquo;t ship — you just deployed.</p>
<p>And deployed isn&rsquo;t delivered — so it can&rsquo;t lead.</p>
<p>The last act of building isn&rsquo;t the final commit. It&rsquo;s what lets the work lead somewhere.</p>
<p>It&rsquo;s the message that says: <em>&ldquo;It&rsquo;s ready. Your turn.&rdquo;</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Explain Your Rules</title>
      <link>https://blog.sebastiansastre.co/posts/explain-your-rules/</link>
      <pubDate>Thu, 12 Feb 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/explain-your-rules/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/explain-your-rules.webp" type="image/webp" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/explain-your-rules.webp" alt=""" loading="lazy" /></figure><p>Every abstraction you introduce is a set of rules in disguise. A class, a module, a function, a service boundary — each one silently declares: <em>here&rsquo;s what you can do, here&rsquo;s what you can&rsquo;t, and here&rsquo;s why.</em></p>
<p>The sign of a mature design is that you can explain those rules in a sentence or two. Not because the problem is simple — but because the solution has been distilled to <em>its essence</em>.</p>
<p>Try it. Pick any structure you&rsquo;ve recently introduced. Explain its rules to a colleague in plain language. If the explanation is short and clear, you&rsquo;ve likely arrived at a good design.</p>
<p>If you can&rsquo;t — if the explanation sprawls, if it requires caveats upon caveats, if the listener&rsquo;s eyes glaze over — that&rsquo;s not a communication problem. That&rsquo;s a design problem.</p>
<p>And it usually means one of three things happened.</p>
<h2 id="a-hacky-solution-was-implemented">A hacky solution was implemented</h2>
<p>The rules are hard to explain because they aren&rsquo;t real rules — they&rsquo;re side effects of a shortcut. <em>&ldquo;Well, you can&rsquo;t call this before that, because internally we&rsquo;re reusing a buffer that gets initialized on the first call, and if you&hellip;&rdquo;</em> — stop. That&rsquo;s not a rule. That&rsquo;s an implementation leak dressed as a contract.</p>
<p>Hacks produce temporal rules: order dependencies, initialization sequences, hidden state mutations. These rules feel complicated to explain because they don&rsquo;t model anything real. They model the path of least resistance someone took at 11 PM on a Friday. The nest of accidental complexity.</p>
<p>A rule that needs three paragraphs to explain is a rule that will be problematic. It&rsquo;s only a matter of time. It will create friction in the wrong places, it will stop enabling you to grow when you&rsquo;ll need it most.</p>
<h2 id="a-can-of-worms-was-accepted">A can of worms was accepted</h2>
<p>Sometimes the rules are hard to explain because the problem space was never properly bounded. You accepted a requirement that seemed small but carried exponential accidental complexity beneath the surface. Now your abstraction has seventeen configuration options, four modes, and a matrix of valid combinations that A) nobody can hold in their head B) are failing to capture the spirit of the narrative of the UX they are supposed to produce.</p>
<p>The rules are complicated because the scope was never challenged or the abstractions are drafty, immature. Good design can have essential complexity, but that demands time, deep thinking, and maturing how the abstractions make the narrative <em>flow</em>. <em>&ldquo;Can it also handle&hellip;?&rdquo;</em> was answered with <em>&ldquo;sure&rdquo;</em> too many times, too soon, in too unconsequentialist a fashion — and now you have a half-working Swiss Army knife that can shoot you in the foot in a combinatorial explosion of seventeen parameters. Functional elegance went down the drain.</p>
<p>When you can&rsquo;t explain the rules, ask yourself: did I accept complexity I should have refused?</p>
<p>Did I think about them in slow motion, deep enough? Enough times? When did I give the universe a chance to inspire me about the edge cases and subtleties around its dynamic behavior? Under heavy load? Under malformed state?</p>
<p>Saying no to a requirement is sometimes the most important design decision you&rsquo;ll make.</p>
<h2 id="an-elegant-design-was-rejected">An elegant design was rejected</h2>
<p>This is the most painful one. Sometimes a clean, simple design existed — one where the rules would have been self-evident — but it was rejected. Too &ldquo;academic.&rdquo; Too much refactoring. Too risky to change the existing structure. Too expensive for this sprint.</p>
<p>The right structure wanted to exist but nobody gave it a chance.</p>
<p>So you bolted the new behavior onto the old structure, and now the rules are a patchwork of the original intent and the compromises that followed. The explanation is long because you&rsquo;re narrating a history of decisions, not describing a coherent design that implemented a coherent story. Technicisms hijacked the UX story — and injected friction into the users&rsquo; and operators&rsquo; journeys.</p>
<p>Every time you explain rules by telling a story — <em>&ldquo;well, originally this was X, but then we needed Y, so now Z&rdquo;</em> — you&rsquo;re confessing that the design was never unified. You aren&rsquo;t stating rules. You&rsquo;re reciting scars.</p>
<hr>
<p>These three failure modes look different on the surface, but they share a root cause: <strong>the design was allowed to grow around the problem instead of through it.</strong></p>
<p>The problem didn&rsquo;t get the love it desperately needed.</p>
<p>The solution got too much emotional attachment too soon.</p>
<p>Rules are the interface between your design and everyone who will ever use it. Including future you. Including the AI that will generate code against it next month. If those rules are simple, your design can be extended, tested, and reasoned about. If they&rsquo;re complex, every future change carries compounding risk that remains silent until something breaks.</p>
<p>Mature design doesn&rsquo;t mean fewer features. It means the features that exist have rules so clear they barely need documentation because they feel natural for the users&rsquo; journeys.</p>
<p>The question isn&rsquo;t whether your code works.</p>
<p>The question is: <strong>can you explain its rules?</strong></p>
<p>Can you take good care of them long term?</p>
<p>If you can&rsquo;t, you already know what to fix — you just need to exercise the muscle of expressing it.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/explain-your-rules.webp" alt=""" loading="lazy" /></figure><p>Every abstraction you introduce is a set of rules in disguise. A class, a module, a function, a service boundary — each one silently declares: <em>here&rsquo;s what you can do, here&rsquo;s what you can&rsquo;t, and here&rsquo;s why.</em></p>
<p>The sign of a mature design is that you can explain those rules in a sentence or two. Not because the problem is simple — but because the solution has been distilled to <em>its essence</em>.</p>
<p>Try it. Pick any structure you&rsquo;ve recently introduced. Explain its rules to a colleague in plain language. If the explanation is short and clear, you&rsquo;ve likely arrived at a good design.</p>
<p>If you can&rsquo;t — if the explanation sprawls, if it requires caveats upon caveats, if the listener&rsquo;s eyes glaze over — that&rsquo;s not a communication problem. That&rsquo;s a design problem.</p>
<p>And it usually means one of three things happened.</p>
<h2 id="a-hacky-solution-was-implemented">A hacky solution was implemented</h2>
<p>The rules are hard to explain because they aren&rsquo;t real rules — they&rsquo;re side effects of a shortcut. <em>&ldquo;Well, you can&rsquo;t call this before that, because internally we&rsquo;re reusing a buffer that gets initialized on the first call, and if you&hellip;&rdquo;</em> — stop. That&rsquo;s not a rule. That&rsquo;s an implementation leak dressed as a contract.</p>
<p>Hacks produce temporal rules: order dependencies, initialization sequences, hidden state mutations. These rules feel complicated to explain because they don&rsquo;t model anything real. They model the path of least resistance someone took at 11 PM on a Friday. The nest of accidental complexity.</p>
<p>A rule that needs three paragraphs to explain is a rule that will be problematic. It&rsquo;s only a matter of time. It will create friction in the wrong places, it will stop enabling you to grow when you&rsquo;ll need it most.</p>
<h2 id="a-can-of-worms-was-accepted">A can of worms was accepted</h2>
<p>Sometimes the rules are hard to explain because the problem space was never properly bounded. You accepted a requirement that seemed small but carried exponential accidental complexity beneath the surface. Now your abstraction has seventeen configuration options, four modes, and a matrix of valid combinations that A) nobody can hold in their head B) are failing to capture the spirit of the narrative of the UX they are supposed to produce.</p>
<p>The rules are complicated because the scope was never challenged or the abstractions are drafty, immature. Good design can have essential complexity, but that demands time, deep thinking, and maturing how the abstractions make the narrative <em>flow</em>. <em>&ldquo;Can it also handle&hellip;?&rdquo;</em> was answered with <em>&ldquo;sure&rdquo;</em> too many times, too soon, in too unconsequentialist a fashion — and now you have a half-working Swiss Army knife that can shoot you in the foot in a combinatorial explosion of seventeen parameters. Functional elegance went down the drain.</p>
<p>When you can&rsquo;t explain the rules, ask yourself: did I accept complexity I should have refused?</p>
<p>Did I think about them in slow motion, deep enough? Enough times? When did I give the universe a chance to inspire me about the edge cases and subtleties around its dynamic behavior? Under heavy load? Under malformed state?</p>
<p>Saying no to a requirement is sometimes the most important design decision you&rsquo;ll make.</p>
<h2 id="an-elegant-design-was-rejected">An elegant design was rejected</h2>
<p>This is the most painful one. Sometimes a clean, simple design existed — one where the rules would have been self-evident — but it was rejected. Too &ldquo;academic.&rdquo; Too much refactoring. Too risky to change the existing structure. Too expensive for this sprint.</p>
<p>The right structure wanted to exist but nobody gave it a chance.</p>
<p>So you bolted the new behavior onto the old structure, and now the rules are a patchwork of the original intent and the compromises that followed. The explanation is long because you&rsquo;re narrating a history of decisions, not describing a coherent design that implemented a coherent story. Technicisms hijacked the UX story — and injected friction into the users&rsquo; and operators&rsquo; journeys.</p>
<p>Every time you explain rules by telling a story — <em>&ldquo;well, originally this was X, but then we needed Y, so now Z&rdquo;</em> — you&rsquo;re confessing that the design was never unified. You aren&rsquo;t stating rules. You&rsquo;re reciting scars.</p>
<hr>
<p>These three failure modes look different on the surface, but they share a root cause: <strong>the design was allowed to grow around the problem instead of through it.</strong></p>
<p>The problem didn&rsquo;t get the love it desperately needed.</p>
<p>The solution got too much emotional attachment too soon.</p>
<p>Rules are the interface between your design and everyone who will ever use it. Including future you. Including the AI that will generate code against it next month. If those rules are simple, your design can be extended, tested, and reasoned about. If they&rsquo;re complex, every future change carries compounding risk that remains silent until something breaks.</p>
<p>Mature design doesn&rsquo;t mean fewer features. It means the features that exist have rules so clear they barely need documentation because they feel natural for the users&rsquo; journeys.</p>
<p>The question isn&rsquo;t whether your code works.</p>
<p>The question is: <strong>can you explain its rules?</strong></p>
<p>Can you take good care of them long term?</p>
<p>If you can&rsquo;t, you already know what to fix — you just need to exercise the muscle of expressing it.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Accelerator and the Brake</title>
      <link>https://blog.sebastiansastre.co/posts/the-accelerator-and-the-brake/</link>
      <pubDate>Thu, 05 Feb 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/the-accelerator-and-the-brake/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/forces-of-progress.webp" type="image/webp" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/forces-of-progress.webp" alt=""" loading="lazy" /></figure><p>Creatives, product developers, and most entrepreneurs believe that if their product is good enough, people will simply buy it. They work themselves to the bone adding features, lowering prices, or polishing the interface, only to watch their business die in the desert of indifference.</p>
<p>The reality is harsher: <strong>you can have the best product in the world and still lose to nothing.</strong></p>
<p>You aren&rsquo;t competing against your direct rival. You aren&rsquo;t fighting the sharks in the &ldquo;red ocean&rdquo; you intentionally sought to avoid. You are competing against fear, laziness, and the &ldquo;we’ve always done it this way&rdquo; mentality.</p>
<p>These forces might not feel material, but if you don&rsquo;t understand the physics of change, you aren’t marketing a product—you’re just shouting at a wall. Or a mountain whose mass you haven&rsquo;t correctly calculated.</p>
<p>Bob Moesta and Clayton Christensen gave us a useful lens: human progress as a balance of four force vectors.</p>
<p class="equation">(Push + Pull) > (Anxiety + Inertia)</p>

<p>Calculate accordingly.</p>
<p>Once that clicks, you start doing napkin math on every human interaction — and you stop guessing why people don’t move. You understand where to find signals, what questions to ask, how to pull new details to expand your understanding of the dynamics at play.</p>
<p>For someone to &ldquo;hire&rdquo; your solution, the forces of movement must outweigh the forces of resistance:</p>
<h3 id="the-accelerator-driving-the-change"><strong>The Accelerator (Driving the change)</strong></h3>
<ol>
<li><strong>The Push (Repulsion):</strong> The kinetic energy of current pain.</li>
</ol>
<ul>
<li><em>Example:</em> You don’t switch your accounting firm because of a &ldquo;nicer logo.&rdquo; You switch because they missed a tax deadline and the government just sent you a fine. That&rsquo;s a heck of a strong repulsive force.</li>
</ul>
<ol start="2">
<li><strong>The Pull (Magnetism):</strong> The gravity of a better life.</li>
</ol>
<ul>
<li><em>Example:</em> It’s not about &ldquo;new software.&rdquo; It’s the vision of finally leaving the office at 5:00 PM because your workflow is actually automated, instead of spending your Sunday nights in a spreadsheet.</li>
</ul>
<h3 id="the-brake-holding-them-back"><strong>The Brake (Holding them back)</strong></h3>
<ol start="3">
<li><strong>The Anxiety (Uncertainty):</strong> The &ldquo;handbrake&rdquo; of the unknown.</li>
</ol>
<ul>
<li><em>Example:</em> The fear that &ldquo;moving my data might corrupt the files.&rdquo; Even if the new tool is better, the fear of a 1% catastrophic error keeps people paralyzed.</li>
</ul>
<ol start="4">
<li><strong>The Inertia (Current Habits):</strong> The comfort of the &ldquo;devil you know.&rdquo;</li>
</ol>
<ul>
<li><em>Example:</em> Using a clunky CRM because &ldquo;everyone already knows the keyboard shortcuts.&rdquo; Habit has a gravity that rewards staying in a mediocre situation.</li>
</ul>
<h3 id="the-engineered-progress"><strong>The Engineered Progress</strong></h3>
<p>Progress isn&rsquo;t a matter of desire; it&rsquo;s a balance of power in a given direction. They&rsquo;re feeling your product like a signpost were you are calling for attention and saying: <em>&ldquo;Hey guys! Here! Let&rsquo;s go this way.&rdquo;</em></p>
<p>It is a fundamental mistake to focus solely on <strong>Attraction</strong>. Is good to have as much as you can it but is not their final destination. Also a &ldquo;nice pull&rdquo; is useless if it is completely overwhelmed by an unaddressed <strong>Anxiety</strong> or the undetected force of <strong>Current Habits</strong>. You are asking your product’s value proposition to do work it realistically cannot do.</p>
<p>Desire validates attraction, but the resistance of comfort neutralizes initiative.</p>
<p>Facilitating change must be intentional, designed, and built. That often means the logistics of removing every stone from your customer’s road before they even try to reach yours. People don&rsquo;t buy a product; they buy an improved version of themselves.</p>
<p><strong>You are either the optimizer of that progress, or you are invisible.</strong></p>
<p>If your customer feels the pain of missing opportunities but is paralyzed by fear, your job isn&rsquo;t to sell them more features. Your job is to dissolve their fear, break their routine, and <strong>prove</strong> that the <em>danger</em> of remaining stagnant is far greater than the risk of moving forward.</p>
<p>You can surrender to your current habits, but your product will keep loosing to inertia.</p>
<p>Stop trying to accelerate while stepping on the brake.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/forces-of-progress.webp" alt=""" loading="lazy" /></figure><p>Creatives, product developers, and most entrepreneurs believe that if their product is good enough, people will simply buy it. They work themselves to the bone adding features, lowering prices, or polishing the interface, only to watch their business die in the desert of indifference.</p>
<p>The reality is harsher: <strong>you can have the best product in the world and still lose to nothing.</strong></p>
<p>You aren&rsquo;t competing against your direct rival. You aren&rsquo;t fighting the sharks in the &ldquo;red ocean&rdquo; you intentionally sought to avoid. You are competing against fear, laziness, and the &ldquo;we’ve always done it this way&rdquo; mentality.</p>
<p>These forces might not feel material, but if you don&rsquo;t understand the physics of change, you aren’t marketing a product—you’re just shouting at a wall. Or a mountain whose mass you haven&rsquo;t correctly calculated.</p>
<p>Bob Moesta and Clayton Christensen gave us a useful lens: human progress as a balance of four force vectors.</p>
<p class="equation">(Push + Pull) > (Anxiety + Inertia)</p>

<p>Calculate accordingly.</p>
<p>Once that clicks, you start doing napkin math on every human interaction — and you stop guessing why people don’t move. You understand where to find signals, what questions to ask, how to pull new details to expand your understanding of the dynamics at play.</p>
<p>For someone to &ldquo;hire&rdquo; your solution, the forces of movement must outweigh the forces of resistance:</p>
<h3 id="the-accelerator-driving-the-change"><strong>The Accelerator (Driving the change)</strong></h3>
<ol>
<li><strong>The Push (Repulsion):</strong> The kinetic energy of current pain.</li>
</ol>
<ul>
<li><em>Example:</em> You don’t switch your accounting firm because of a &ldquo;nicer logo.&rdquo; You switch because they missed a tax deadline and the government just sent you a fine. That&rsquo;s a heck of a strong repulsive force.</li>
</ul>
<ol start="2">
<li><strong>The Pull (Magnetism):</strong> The gravity of a better life.</li>
</ol>
<ul>
<li><em>Example:</em> It’s not about &ldquo;new software.&rdquo; It’s the vision of finally leaving the office at 5:00 PM because your workflow is actually automated, instead of spending your Sunday nights in a spreadsheet.</li>
</ul>
<h3 id="the-brake-holding-them-back"><strong>The Brake (Holding them back)</strong></h3>
<ol start="3">
<li><strong>The Anxiety (Uncertainty):</strong> The &ldquo;handbrake&rdquo; of the unknown.</li>
</ol>
<ul>
<li><em>Example:</em> The fear that &ldquo;moving my data might corrupt the files.&rdquo; Even if the new tool is better, the fear of a 1% catastrophic error keeps people paralyzed.</li>
</ul>
<ol start="4">
<li><strong>The Inertia (Current Habits):</strong> The comfort of the &ldquo;devil you know.&rdquo;</li>
</ol>
<ul>
<li><em>Example:</em> Using a clunky CRM because &ldquo;everyone already knows the keyboard shortcuts.&rdquo; Habit has a gravity that rewards staying in a mediocre situation.</li>
</ul>
<h3 id="the-engineered-progress"><strong>The Engineered Progress</strong></h3>
<p>Progress isn&rsquo;t a matter of desire; it&rsquo;s a balance of power in a given direction. They&rsquo;re feeling your product like a signpost were you are calling for attention and saying: <em>&ldquo;Hey guys! Here! Let&rsquo;s go this way.&rdquo;</em></p>
<p>It is a fundamental mistake to focus solely on <strong>Attraction</strong>. Is good to have as much as you can it but is not their final destination. Also a &ldquo;nice pull&rdquo; is useless if it is completely overwhelmed by an unaddressed <strong>Anxiety</strong> or the undetected force of <strong>Current Habits</strong>. You are asking your product’s value proposition to do work it realistically cannot do.</p>
<p>Desire validates attraction, but the resistance of comfort neutralizes initiative.</p>
<p>Facilitating change must be intentional, designed, and built. That often means the logistics of removing every stone from your customer’s road before they even try to reach yours. People don&rsquo;t buy a product; they buy an improved version of themselves.</p>
<p><strong>You are either the optimizer of that progress, or you are invisible.</strong></p>
<p>If your customer feels the pain of missing opportunities but is paralyzed by fear, your job isn&rsquo;t to sell them more features. Your job is to dissolve their fear, break their routine, and <strong>prove</strong> that the <em>danger</em> of remaining stagnant is far greater than the risk of moving forward.</p>
<p>You can surrender to your current habits, but your product will keep loosing to inertia.</p>
<p>Stop trying to accelerate while stepping on the brake.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Relative Trap</title>
      <link>https://blog.sebastiansastre.co/posts/the-relative-trap/</link>
      <pubDate>Mon, 26 Jan 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/the-relative-trap/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/the-relative-trap.webp" type="image/webp" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/the-relative-trap.webp" alt=""" loading="lazy" /></figure><p>We’ve been sold the idea that everything is relative. That your reality and mine are parallel bubbles, and that every symbol is an infinite well of interpretations. It sounds inclusive. It sounds intellectual. But it’s a trap.</p>
<p>This trap creates an illusion of progress. Inside, you are sprinting through an infinite loop of meaning; outside, you are frozen. You are busy, but you aren&rsquo;t moving.</p>
<p>The problem is that while symbols are polysemous and they can have too many interpretations, evidence is finite. You can debate the meaning of a map forever, but if the map shows a cliff and you keep walking, gravity won&rsquo;t pause to interpret your narrative.</p>
<p>&ldquo;Analysis paralysis&rdquo; is born from ignoring the fact that reality is in charge. Mental health isn&rsquo;t about being right; it&rsquo;s about loving reality unconditionally — especially when it&rsquo;s uncomfortable.</p>
<p>Without a frame, an idea isn&rsquo;t useful; it&rsquo;s just noise. An automated flood of perspectives paralyzes the intimate process of making meaning. Abstractions that can mean anything end up meaning nothing. We get stuck in an infinite loop of interpretation.</p>
<p>In software, this is known as accidental complexity: the self-serving bloat that smothers the essential complexity required for a system to function. While essential complexity drives useful behavior, accidental complexity just consumes resources to justify its own existence.</p>
<p>These abstractions are mental parasites. They feed on themselves without delivering a single gram of value to real life.</p>
<p>Only when you accept constraints — the rules of the real game vs. the rules of the pretend game — does an idea become practical. Claiming that &ldquo;everything is relative&rdquo; is a cheap trick: an absolute premise masquerading as humility to evade scrutiny to install a mistake.</p>
<p>Vagueness is an intellectual smoke screen. Ambiguity is the refuge of those afraid to be wrong. If you aren&rsquo;t willing to despise the false and accept that some things are simply true, you will never do anything that matters.</p>
<p>Reality is not an opinion. It&rsquo;s the ground beneath your feet.</p>
<p>Stop debating the map and start walking on solid premises that defend themselves through self-evidence — or accept that you prefer the brief illusion of safety of a paralysis that history will simply evaporate.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/the-relative-trap.webp" alt=""" loading="lazy" /></figure><p>We’ve been sold the idea that everything is relative. That your reality and mine are parallel bubbles, and that every symbol is an infinite well of interpretations. It sounds inclusive. It sounds intellectual. But it’s a trap.</p>
<p>This trap creates an illusion of progress. Inside, you are sprinting through an infinite loop of meaning; outside, you are frozen. You are busy, but you aren&rsquo;t moving.</p>
<p>The problem is that while symbols are polysemous and they can have too many interpretations, evidence is finite. You can debate the meaning of a map forever, but if the map shows a cliff and you keep walking, gravity won&rsquo;t pause to interpret your narrative.</p>
<p>&ldquo;Analysis paralysis&rdquo; is born from ignoring the fact that reality is in charge. Mental health isn&rsquo;t about being right; it&rsquo;s about loving reality unconditionally — especially when it&rsquo;s uncomfortable.</p>
<p>Without a frame, an idea isn&rsquo;t useful; it&rsquo;s just noise. An automated flood of perspectives paralyzes the intimate process of making meaning. Abstractions that can mean anything end up meaning nothing. We get stuck in an infinite loop of interpretation.</p>
<p>In software, this is known as accidental complexity: the self-serving bloat that smothers the essential complexity required for a system to function. While essential complexity drives useful behavior, accidental complexity just consumes resources to justify its own existence.</p>
<p>These abstractions are mental parasites. They feed on themselves without delivering a single gram of value to real life.</p>
<p>Only when you accept constraints — the rules of the real game vs. the rules of the pretend game — does an idea become practical. Claiming that &ldquo;everything is relative&rdquo; is a cheap trick: an absolute premise masquerading as humility to evade scrutiny to install a mistake.</p>
<p>Vagueness is an intellectual smoke screen. Ambiguity is the refuge of those afraid to be wrong. If you aren&rsquo;t willing to despise the false and accept that some things are simply true, you will never do anything that matters.</p>
<p>Reality is not an opinion. It&rsquo;s the ground beneath your feet.</p>
<p>Stop debating the map and start walking on solid premises that defend themselves through self-evidence — or accept that you prefer the brief illusion of safety of a paralysis that history will simply evaporate.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Model the Reality, Not the Patterns</title>
      <link>https://blog.sebastiansastre.co/posts/model-the-reality-not-the-patterns/</link>
      <pubDate>Wed, 21 Jan 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/model-the-reality-not-the-patterns/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/aristotle-the-software-architect.webp" type="image/webp" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/aristotle-the-software-architect.webp" alt=""" loading="lazy" /></figure><p>I was about to make a mistake that would have cost me months of refactoring — and a friend&rsquo;s question saved me. It was early in the 2000&rsquo;s but I vividly remember a project I worked on where everything changed for me. I was deep into implementing the system, and I had a design doubt that, although I expressed it in a rudimentary way like &ldquo;how do I do this?&rdquo;, I knew it would condition the future of that software. I&rsquo;d just devoured The Design Patterns Smalltalk Companion and Smalltalk Best Practice Patterns by Kent Beck, and I was eager for the ideas in those books, ready to fit patterns like Singleton, Command, or Strategy into every corner of the design.</p>
<p>At the time, we used to consult colleagues and friends for design feedback. While I enthusiastically explained to one how one pattern would solve this problem and another would fit perfectly into that other part, this friend interrupted me with a phrase that stuck in my mind: &ldquo;But why, instead of trying to fit patterns into your design, don&rsquo;t you focus on properly modeling what your models should do? From that, you might see known patterns emerge, but it&rsquo;s from modeling correctly that they arise.&rdquo;</p>
<p>Everything seemed to happen in slow motion at that moment.</p>
<p>It wasn&rsquo;t about forcing preconceived technical structures from the outside in, but about capturing the essence of the real world that the software aimed to represent and modeling it modestly from its parts toward the totality of the desired behavior.</p>
<p>This meant refraining from overly premature bottom-up solution building and instead promoting a modest, top-down-inspired bottom-up approach.</p>
<p>My confused ego felt the cold bucket of water, but my inner Aristotle enthusiastically approved the insight of guiding the design down that path.</p>
<p>This insight not only transformed my approach to programming but also showed me how software design can be more resilient over time, flexible, and maintainable.</p>
<p>And it&rsquo;s obvious why.</p>
<p>Implementation details can change like a library update, but the essence, the reasons for something&rsquo;s existence, is much harder to change. If the structure successfully captures that, it will be much more long-lived and resistant to the passage of time.</p>
<p>This principle becomes even more relevant now that AIs have inverted the cost of execution.</p>
<p>When code generation is cheap, design quality becomes your competitive advantage.</p>
<p>We&rsquo;ve always been accustomed and adapted to the idea that executing a project is the expensive part. &ldquo;Everything&rdquo; in the industry is optimizing for that. But with synthetic assistants, producing functional code and execution is no longer the most costly part.</p>
<h2 id="reality-vs-necessaryunnecessary-abstractions">Reality vs. Necessary/Unnecessary Abstractions</h2>
<p>At its core, software is nothing more than a simulation of the real world. A digital mirror that reflects processes, entities, and relationships in the domain we&rsquo;re trying to solve. When we prioritize modeling this reality—the actors, their responsibilities, and inherent behaviors—we create systems that flow naturally, like a river following its course.</p>
<p>Every system is necessarily an abstraction of reality that we call &ldquo;works well&rdquo; when it models it with high fidelity for what we wanted to manage. But if it has no bugs and its utilitarian purpose doesn&rsquo;t distract us, we quickly forget that it&rsquo;s a reflection of reality. That it&rsquo;s still a structural and dynamic model of real-world processes and objects with their natural tendencies.</p>
<h2 id="invention-vs-discovery">Invention vs. Discovery</h2>
<p>Kent Beck captured it perfectly in his works: &ldquo;Patterns are not invented; they are discovered.&rdquo; They aren&rsquo;t recipes we apply from a book to impress, but &ldquo;accidental consequences of design&rdquo; that appear organically when proper and complete modeling has been done. Forcing a pattern, like imposing a Singleton where it wasn&rsquo;t entirely necessary, creates a restriction that will have consequences.</p>
<p>Speaking of Singleton, this one in particular doesn&rsquo;t let you scale horizontally whatever you&rsquo;re modeling. Almost everything you initially thought to solve with a Singleton was actually a way to move the project forward by referencing that object from different places of the codebase, because at the start of the project, it&rsquo;s not so clear how those &ldquo;different places&rdquo; can be properly coupled. You solve it with a Singleton, but later you figured out it was better to instantiate it normally at startup and pass it downstream to the modules that need it.</p>
<p>The sequence in which your mind found the best abstractions chain was not linear and this is natural for us. But we need to be mindful now because AIs have a different nature and might not have that restriction (they have other limitations of their own).</p>
<p>And this philosophy transcends languages: in Smalltalk, where everything is an object, it&rsquo;s easy to fall into the temptation of patterns by default. But in other non-pure languages too. You can perfectly do all the wrong Separations Of Concerns in your Rust modules.</p>
<p>Even in generative AI environments, the principle is not only the same but its value is renewed and amplified.</p>
<p>Ask your design: &ldquo;Does this reflect the real domain, or is it a forced abstraction?&rdquo; By doing so, you&rsquo;ll see how patterns emerge on their own, making your design more future-proof—capable of evolving without breaking—and flexible to adapt to new requirements without massive refactorings.</p>
<p>Contrast this with a hacked design: forced patterns create high coupling and scattered logic, confusing even advanced AIs. It&rsquo;s going to leave your project slower and more complicated to maintain. In a thought experiment, imagine asking an AI to add a feature to an e-commerce system. If the domain is well-modeled—products that &ldquo;know&rdquo; how to promote themselves, carts that calculate totals on their own—the AI generates precise and maintainable code. If not, it ends up patching hacks, perpetuating technical debt despite fitting some scattered unit tests that pass and tell you everything&rsquo;s fine. Maybe in that commit, but in the next feature you want to add, you&rsquo;ll feel bogged down.</p>
<h2 id="barriers">Barriers</h2>
<p>The pressure of deadlines pushes us toward quick solutions: &ldquo;I couple this here from another module that has nothing to do with it and solve it today.&rdquo; Not all tech debt is bad, but keeping it under control requires being attentive to its consequences.</p>
<p>Another barrier is technical inertia: in large teams, it&rsquo;s tempting to copy existing structures, even if they don&rsquo;t model the current domain. I do it this way because they solved it that way over there, &ldquo;it&rsquo;s safe.&rdquo; And with AIs, some think &ldquo;the AI will fix it,&rdquo; but poor modeling only amplifies problems in automatic generations.</p>
<p>A particularly subtle barrier is how our ability to solve technical problems—those ingenious hacks that save the day with &ldquo;clever&rdquo; implementations—can negatively condition high-level design. It&rsquo;s easy to fall into the trap of thinking in bottom-up solutions, where low-level code dictates the architecture, instead of top-down, where the domain guides the design. This leads to rigid systems, where the technical &ldquo;how&rdquo; eclipses the &ldquo;what&rdquo; of the real domain. The details, convenient at first, later distance you from reality.</p>
<h2 id="techniques-to-avoid-falling-into-this-problem">Techniques to avoid falling into this problem</h2>
<p><strong>Explicit Separation of Phases</strong>: Divide the process into clear stages. First, dedicate exclusive time to modeling the domain without touching code: use whiteboards, diagrams, or even conversations with stakeholders to capture real entities and flows. From those flows, you can get an idea of which methods are essential, thus a notion of what would result in a minimal, elegant, and maintainable API. Only then, implement. This defends you from low-level details invading the high level. For AIs, ask them to generate domain models based on natural descriptions before code, and have them unblock you or give you more than one approach to the solution that solves a problem.</p>
<p><strong>Competing Design Ideation</strong>: Before committing to an implementation, create an ideation phase where multiple design approaches compete for approval. This used to be costly and laborious — which explains the natural resistance to doing it — but it&rsquo;s become brutally cheap now. Generate at least three different ways to model the solution to the same problem, ranging from minimalistic to comprehensive, each with its own trade-offs and domain alignment. Make these design ideas explicitly compete by evaluating them against domain fidelity, maintainability, and future flexibility. They will be fantastic conversation starters between engineers. It prevents premature commitment to a single approach that might be a forced pattern in disguise. The winning design should emerge from how well it captures reality and prepares you for the future, not from technical convenience. With AIs, ask them to generate multiple architectural approaches for the same problem, then contrast them. Have them argue for and against each approach from a domain modeling perspective. This competitive ideation surfaces hidden assumptions and forces you to justify design choices based on reality before patterns.</p>
<p><strong>Test-Driven Development with Focus on Behaviors (BDD)</strong>: Write tests that describe domain behaviors in natural language (using tools like Cucumber or SpecFlow). This forces you to think about &ldquo;what the system does&rdquo; from the user or domain perspective, not &ldquo;how I implement it.&rdquo; Your low-level skills are used to pass the tests, but they don&rsquo;t dictate the design. AIs can generate these initial tests, reinforcing the high-level focus. Try having an AI make you a PRD and ask it to generate user stories and those tests. They usually do a great job with that part.</p>
<p><strong>Analogies and Real-World Metaphors</strong>: Before designing, relate the problem to a non-technical scenario. For example, compare an order system to a real restaurant: Who handles what? This elevates thinking above code-specific hacks. It makes you think about the &ldquo;universality&rdquo; of a certain process, and your mind will be more eager to capture its essence. Ask AIs for metaphors, have them give you several to expand perspectives. It doesn&rsquo;t matter if not all fit; the technique is that by reading them, you&rsquo;ll find the one that fits best faster because you have better context than the AIs.</p>
<p><strong>Retrospectives and Design Journaling</strong>: At the end of each iteration, ask yourself: &ldquo;Does my design reflect the domain or my technical preferences?&rdquo; Keep a journal of decisions to detect conditioning patterns. In my repos, I even use a DECISIONS.md for this. In teams with AIs, use prompts so they analyze your code and point out if the low level dominates.</p>
<p><strong>Exercises in Refactoring Toward Domain</strong>: Take existing code with hacks and refactor it focusing only on moving behaviors to domain-correct actors, ignoring premature optimizations. Do it as a regular practice. Ask an AI to propose refactorings, comparing versions to internalize the insight. Try telling it that you have a preference for &ldquo;One-way dependency discipline.&rdquo; It will definitely propose things that improve Separation of Concerns and how modules depend on each other.</p>
<p><strong>Interdisciplinary Collaboration</strong>: Involve non-programmers (like domain experts) in modeling sessions. Their inputs keep the focus on reality, diluting technical bias. If it&rsquo;s hard to schedule, customized AIs can simulate these experts via role-playing in prompts.</p>
<p>These techniques aren&rsquo;t just tricks; they&rsquo;re tools to cultivate a mindset where the domain rules and technical details serve, not the other way around. By applying them, you not only avoid common traps but also prepare your systems for a future where humans and AIs co-create without friction, with designs that evolve gracefully instead of breaking under the weight of accumulated hacks. It&rsquo;s like going from being a reactive coder to a visionary architect, where every decision honors the reality of the problem.</p>
<p>The understandability of your basecode becomes a feature.</p>
<p>A good design is never an accident.</p>
<p>It&rsquo;s always deeply intentional.</p>
<p>And often, the best one is discovered after removing everything that&rsquo;s not essential.</p>
<p>Does your inner Aristotle approve?</p>
<p>Before writing a single line of code, start your next feature by asking:</p>
<ul>
<li>&lsquo;What is the real-world process I&rsquo;m modeling?&rsquo;</li>
<li>&lsquo;What are the restrictions I&rsquo;ll be imposing with this necessarily incomplete model of the real-world I&rsquo;m coding?&rsquo;</li>
</ul>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/aristotle-the-software-architect.webp" alt=""" loading="lazy" /></figure><p>I was about to make a mistake that would have cost me months of refactoring — and a friend&rsquo;s question saved me. It was early in the 2000&rsquo;s but I vividly remember a project I worked on where everything changed for me. I was deep into implementing the system, and I had a design doubt that, although I expressed it in a rudimentary way like &ldquo;how do I do this?&rdquo;, I knew it would condition the future of that software. I&rsquo;d just devoured The Design Patterns Smalltalk Companion and Smalltalk Best Practice Patterns by Kent Beck, and I was eager for the ideas in those books, ready to fit patterns like Singleton, Command, or Strategy into every corner of the design.</p>
<p>At the time, we used to consult colleagues and friends for design feedback. While I enthusiastically explained to one how one pattern would solve this problem and another would fit perfectly into that other part, this friend interrupted me with a phrase that stuck in my mind: &ldquo;But why, instead of trying to fit patterns into your design, don&rsquo;t you focus on properly modeling what your models should do? From that, you might see known patterns emerge, but it&rsquo;s from modeling correctly that they arise.&rdquo;</p>
<p>Everything seemed to happen in slow motion at that moment.</p>
<p>It wasn&rsquo;t about forcing preconceived technical structures from the outside in, but about capturing the essence of the real world that the software aimed to represent and modeling it modestly from its parts toward the totality of the desired behavior.</p>
<p>This meant refraining from overly premature bottom-up solution building and instead promoting a modest, top-down-inspired bottom-up approach.</p>
<p>My confused ego felt the cold bucket of water, but my inner Aristotle enthusiastically approved the insight of guiding the design down that path.</p>
<p>This insight not only transformed my approach to programming but also showed me how software design can be more resilient over time, flexible, and maintainable.</p>
<p>And it&rsquo;s obvious why.</p>
<p>Implementation details can change like a library update, but the essence, the reasons for something&rsquo;s existence, is much harder to change. If the structure successfully captures that, it will be much more long-lived and resistant to the passage of time.</p>
<p>This principle becomes even more relevant now that AIs have inverted the cost of execution.</p>
<p>When code generation is cheap, design quality becomes your competitive advantage.</p>
<p>We&rsquo;ve always been accustomed and adapted to the idea that executing a project is the expensive part. &ldquo;Everything&rdquo; in the industry is optimizing for that. But with synthetic assistants, producing functional code and execution is no longer the most costly part.</p>
<h2 id="reality-vs-necessaryunnecessary-abstractions">Reality vs. Necessary/Unnecessary Abstractions</h2>
<p>At its core, software is nothing more than a simulation of the real world. A digital mirror that reflects processes, entities, and relationships in the domain we&rsquo;re trying to solve. When we prioritize modeling this reality—the actors, their responsibilities, and inherent behaviors—we create systems that flow naturally, like a river following its course.</p>
<p>Every system is necessarily an abstraction of reality that we call &ldquo;works well&rdquo; when it models it with high fidelity for what we wanted to manage. But if it has no bugs and its utilitarian purpose doesn&rsquo;t distract us, we quickly forget that it&rsquo;s a reflection of reality. That it&rsquo;s still a structural and dynamic model of real-world processes and objects with their natural tendencies.</p>
<h2 id="invention-vs-discovery">Invention vs. Discovery</h2>
<p>Kent Beck captured it perfectly in his works: &ldquo;Patterns are not invented; they are discovered.&rdquo; They aren&rsquo;t recipes we apply from a book to impress, but &ldquo;accidental consequences of design&rdquo; that appear organically when proper and complete modeling has been done. Forcing a pattern, like imposing a Singleton where it wasn&rsquo;t entirely necessary, creates a restriction that will have consequences.</p>
<p>Speaking of Singleton, this one in particular doesn&rsquo;t let you scale horizontally whatever you&rsquo;re modeling. Almost everything you initially thought to solve with a Singleton was actually a way to move the project forward by referencing that object from different places of the codebase, because at the start of the project, it&rsquo;s not so clear how those &ldquo;different places&rdquo; can be properly coupled. You solve it with a Singleton, but later you figured out it was better to instantiate it normally at startup and pass it downstream to the modules that need it.</p>
<p>The sequence in which your mind found the best abstractions chain was not linear and this is natural for us. But we need to be mindful now because AIs have a different nature and might not have that restriction (they have other limitations of their own).</p>
<p>And this philosophy transcends languages: in Smalltalk, where everything is an object, it&rsquo;s easy to fall into the temptation of patterns by default. But in other non-pure languages too. You can perfectly do all the wrong Separations Of Concerns in your Rust modules.</p>
<p>Even in generative AI environments, the principle is not only the same but its value is renewed and amplified.</p>
<p>Ask your design: &ldquo;Does this reflect the real domain, or is it a forced abstraction?&rdquo; By doing so, you&rsquo;ll see how patterns emerge on their own, making your design more future-proof—capable of evolving without breaking—and flexible to adapt to new requirements without massive refactorings.</p>
<p>Contrast this with a hacked design: forced patterns create high coupling and scattered logic, confusing even advanced AIs. It&rsquo;s going to leave your project slower and more complicated to maintain. In a thought experiment, imagine asking an AI to add a feature to an e-commerce system. If the domain is well-modeled—products that &ldquo;know&rdquo; how to promote themselves, carts that calculate totals on their own—the AI generates precise and maintainable code. If not, it ends up patching hacks, perpetuating technical debt despite fitting some scattered unit tests that pass and tell you everything&rsquo;s fine. Maybe in that commit, but in the next feature you want to add, you&rsquo;ll feel bogged down.</p>
<h2 id="barriers">Barriers</h2>
<p>The pressure of deadlines pushes us toward quick solutions: &ldquo;I couple this here from another module that has nothing to do with it and solve it today.&rdquo; Not all tech debt is bad, but keeping it under control requires being attentive to its consequences.</p>
<p>Another barrier is technical inertia: in large teams, it&rsquo;s tempting to copy existing structures, even if they don&rsquo;t model the current domain. I do it this way because they solved it that way over there, &ldquo;it&rsquo;s safe.&rdquo; And with AIs, some think &ldquo;the AI will fix it,&rdquo; but poor modeling only amplifies problems in automatic generations.</p>
<p>A particularly subtle barrier is how our ability to solve technical problems—those ingenious hacks that save the day with &ldquo;clever&rdquo; implementations—can negatively condition high-level design. It&rsquo;s easy to fall into the trap of thinking in bottom-up solutions, where low-level code dictates the architecture, instead of top-down, where the domain guides the design. This leads to rigid systems, where the technical &ldquo;how&rdquo; eclipses the &ldquo;what&rdquo; of the real domain. The details, convenient at first, later distance you from reality.</p>
<h2 id="techniques-to-avoid-falling-into-this-problem">Techniques to avoid falling into this problem</h2>
<p><strong>Explicit Separation of Phases</strong>: Divide the process into clear stages. First, dedicate exclusive time to modeling the domain without touching code: use whiteboards, diagrams, or even conversations with stakeholders to capture real entities and flows. From those flows, you can get an idea of which methods are essential, thus a notion of what would result in a minimal, elegant, and maintainable API. Only then, implement. This defends you from low-level details invading the high level. For AIs, ask them to generate domain models based on natural descriptions before code, and have them unblock you or give you more than one approach to the solution that solves a problem.</p>
<p><strong>Competing Design Ideation</strong>: Before committing to an implementation, create an ideation phase where multiple design approaches compete for approval. This used to be costly and laborious — which explains the natural resistance to doing it — but it&rsquo;s become brutally cheap now. Generate at least three different ways to model the solution to the same problem, ranging from minimalistic to comprehensive, each with its own trade-offs and domain alignment. Make these design ideas explicitly compete by evaluating them against domain fidelity, maintainability, and future flexibility. They will be fantastic conversation starters between engineers. It prevents premature commitment to a single approach that might be a forced pattern in disguise. The winning design should emerge from how well it captures reality and prepares you for the future, not from technical convenience. With AIs, ask them to generate multiple architectural approaches for the same problem, then contrast them. Have them argue for and against each approach from a domain modeling perspective. This competitive ideation surfaces hidden assumptions and forces you to justify design choices based on reality before patterns.</p>
<p><strong>Test-Driven Development with Focus on Behaviors (BDD)</strong>: Write tests that describe domain behaviors in natural language (using tools like Cucumber or SpecFlow). This forces you to think about &ldquo;what the system does&rdquo; from the user or domain perspective, not &ldquo;how I implement it.&rdquo; Your low-level skills are used to pass the tests, but they don&rsquo;t dictate the design. AIs can generate these initial tests, reinforcing the high-level focus. Try having an AI make you a PRD and ask it to generate user stories and those tests. They usually do a great job with that part.</p>
<p><strong>Analogies and Real-World Metaphors</strong>: Before designing, relate the problem to a non-technical scenario. For example, compare an order system to a real restaurant: Who handles what? This elevates thinking above code-specific hacks. It makes you think about the &ldquo;universality&rdquo; of a certain process, and your mind will be more eager to capture its essence. Ask AIs for metaphors, have them give you several to expand perspectives. It doesn&rsquo;t matter if not all fit; the technique is that by reading them, you&rsquo;ll find the one that fits best faster because you have better context than the AIs.</p>
<p><strong>Retrospectives and Design Journaling</strong>: At the end of each iteration, ask yourself: &ldquo;Does my design reflect the domain or my technical preferences?&rdquo; Keep a journal of decisions to detect conditioning patterns. In my repos, I even use a DECISIONS.md for this. In teams with AIs, use prompts so they analyze your code and point out if the low level dominates.</p>
<p><strong>Exercises in Refactoring Toward Domain</strong>: Take existing code with hacks and refactor it focusing only on moving behaviors to domain-correct actors, ignoring premature optimizations. Do it as a regular practice. Ask an AI to propose refactorings, comparing versions to internalize the insight. Try telling it that you have a preference for &ldquo;One-way dependency discipline.&rdquo; It will definitely propose things that improve Separation of Concerns and how modules depend on each other.</p>
<p><strong>Interdisciplinary Collaboration</strong>: Involve non-programmers (like domain experts) in modeling sessions. Their inputs keep the focus on reality, diluting technical bias. If it&rsquo;s hard to schedule, customized AIs can simulate these experts via role-playing in prompts.</p>
<p>These techniques aren&rsquo;t just tricks; they&rsquo;re tools to cultivate a mindset where the domain rules and technical details serve, not the other way around. By applying them, you not only avoid common traps but also prepare your systems for a future where humans and AIs co-create without friction, with designs that evolve gracefully instead of breaking under the weight of accumulated hacks. It&rsquo;s like going from being a reactive coder to a visionary architect, where every decision honors the reality of the problem.</p>
<p>The understandability of your basecode becomes a feature.</p>
<p>A good design is never an accident.</p>
<p>It&rsquo;s always deeply intentional.</p>
<p>And often, the best one is discovered after removing everything that&rsquo;s not essential.</p>
<p>Does your inner Aristotle approve?</p>
<p>Before writing a single line of code, start your next feature by asking:</p>
<ul>
<li>&lsquo;What is the real-world process I&rsquo;m modeling?&rsquo;</li>
<li>&lsquo;What are the restrictions I&rsquo;ll be imposing with this necessarily incomplete model of the real-world I&rsquo;m coding?&rsquo;</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>You Can&#39;t Negotiate Genuine Desire</title>
      <link>https://blog.sebastiansastre.co/posts/you-cant-negotiate-genuine-desire/</link>
      <pubDate>Mon, 04 Sep 2023 20:54:19 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/you-cant-negotiate-genuine-desire/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/desireIsNotNegotiable.jpg" type="image/jpeg" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/desireIsNotNegotiable.jpg" alt=""" loading="lazy" /></figure><p>In the vast landscape of business, one truth remains immutable: you can&rsquo;t negotiate genuine desire. It&rsquo;s a lesson that traditional industries have tried to bend to their will, and even in the dynamic world of startups and tech products, it&rsquo;s a concept that refuses to yield.</p>
<p>Consider, for a moment, the conventional approach to business. Picture a car dealership with a pushy salesperson who insists that their shiny sedan is exactly what you need. You may relent and make the purchase, but your heart doesn&rsquo;t truly desire that vehicle. Sure, it serves a purpose, but it doesn&rsquo;t inspire genuine passion</p>
<p>Now, imagine walking into an upscale fashion boutique. The well-designed storefront and carefully curated displays invite you to explore. No one pressures you to buy, but you find yourself irresistibly drawn to a stunning dress. It&rsquo;s not because someone coerced you; it&rsquo;s because the product spoke to your imagination using it, your sense of style and your possible expressions when using it. That&rsquo;s genuine desire in action.</p>
<p>For startups and tech products, the same principle applies. Let&rsquo;s say you&rsquo;ve developed an innovative mobile app, but your initial marketing strategy revolves around intrusive pop-up ads and persistent notifications. You may see a temporary surge in downloads, but are these users genuinely desiring your app? More often than not, they&rsquo;re merely capitulating to persistent marketing tactics. What do you think the churn would be?</p>
<p>On the other hand, consider the story of a small tech startup that created a x10 solution as an app. Instead of bombarding potential users with ads, they offered a series of well-crafted blog posts and videos explaining how their app could transform daily life. It will help its users to get specific jobs done. They nurtured a community of early adopters, actively engaging with feedback and suggestions. Soon, users weren&rsquo;t just downloading the app; they were enthusiastically sharing it with friends. Why? Because the app genuinely resonated with their needs and desires.</p>
<p>Something that is key for a successful product is creating content that provokes attractiveness. It works on seduction rather than pushing an outcome. You cannot negotiate desire because desire happens inside your audience, not you.</p>
<p>Are you listening to your audience? Can you express their needs in better terms than they do?</p>
<p>Can you craft the narratives that resonate emotionally with them?</p>
<p>What do you stand for? Is that desirable for them?</p>
<p>You don&rsquo;t have control over the audience needs and desires.</p>
<p>Good news is that you do have control over designing environments where remarkable solutions can be experienced and curiosity can be awaken.</p>
<p>And that&rsquo;s how it starts.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/desireIsNotNegotiable.jpg" alt=""" loading="lazy" /></figure><p>In the vast landscape of business, one truth remains immutable: you can&rsquo;t negotiate genuine desire. It&rsquo;s a lesson that traditional industries have tried to bend to their will, and even in the dynamic world of startups and tech products, it&rsquo;s a concept that refuses to yield.</p>
<p>Consider, for a moment, the conventional approach to business. Picture a car dealership with a pushy salesperson who insists that their shiny sedan is exactly what you need. You may relent and make the purchase, but your heart doesn&rsquo;t truly desire that vehicle. Sure, it serves a purpose, but it doesn&rsquo;t inspire genuine passion</p>
<p>Now, imagine walking into an upscale fashion boutique. The well-designed storefront and carefully curated displays invite you to explore. No one pressures you to buy, but you find yourself irresistibly drawn to a stunning dress. It&rsquo;s not because someone coerced you; it&rsquo;s because the product spoke to your imagination using it, your sense of style and your possible expressions when using it. That&rsquo;s genuine desire in action.</p>
<p>For startups and tech products, the same principle applies. Let&rsquo;s say you&rsquo;ve developed an innovative mobile app, but your initial marketing strategy revolves around intrusive pop-up ads and persistent notifications. You may see a temporary surge in downloads, but are these users genuinely desiring your app? More often than not, they&rsquo;re merely capitulating to persistent marketing tactics. What do you think the churn would be?</p>
<p>On the other hand, consider the story of a small tech startup that created a x10 solution as an app. Instead of bombarding potential users with ads, they offered a series of well-crafted blog posts and videos explaining how their app could transform daily life. It will help its users to get specific jobs done. They nurtured a community of early adopters, actively engaging with feedback and suggestions. Soon, users weren&rsquo;t just downloading the app; they were enthusiastically sharing it with friends. Why? Because the app genuinely resonated with their needs and desires.</p>
<p>Something that is key for a successful product is creating content that provokes attractiveness. It works on seduction rather than pushing an outcome. You cannot negotiate desire because desire happens inside your audience, not you.</p>
<p>Are you listening to your audience? Can you express their needs in better terms than they do?</p>
<p>Can you craft the narratives that resonate emotionally with them?</p>
<p>What do you stand for? Is that desirable for them?</p>
<p>You don&rsquo;t have control over the audience needs and desires.</p>
<p>Good news is that you do have control over designing environments where remarkable solutions can be experienced and curiosity can be awaken.</p>
<p>And that&rsquo;s how it starts.</p>
]]></content:encoded>
    </item>
    <item>
      <title>SOLID: Crafting Software To Conquer Complexity</title>
      <link>https://blog.sebastiansastre.co/posts/solid-crafting-software-to-conquer-complexity/</link>
      <pubDate>Mon, 28 Aug 2023 20:54:07 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/solid-crafting-software-to-conquer-complexity/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/conquerComplexity.jpg" type="image/jpeg" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/conquerComplexity.jpg" alt=""" loading="lazy" /></figure><p>In the realm of software development, crafting code that remains maintainable, flexible, and scalable stands as an utmost priority. And when delving into the concept of scalability, I&rsquo;m considering not only the system&rsquo;s load capacity but also your ability to maintain it, essentially wielding it as a tool to conquer complexity. To achieve this, developers often embrace design principles that steer them in writing this type of high-quality code. One such set of principles is known as SOLID, an acronym representing five essential tenets in object-oriented programming.</p>
<p>Let&rsquo;s delve into what each of these principles entails:</p>
<h3 id="1-single-responsibility-principle-srp">1. Single Responsibility Principle (SRP)</h3>
<p>The Single Responsibility Principle emphasizes that a class should have only one reason to change. Intentionally resisting designs that would mix responsibilities. In other words, a class should always have only one responsibility. This principle encourages us to break down complex classes into smaller, focused ones, each responsible for a single task. By adhering to SRP, code becomes more modular and easier to maintain, as changes in one area of functionality are less likely to affect unrelated parts. Discipline on this will enable your team to architect a maintainable and flexible dependency tree.</p>
<h3 id="2-openclosed-principle-ocp">2. Open/Closed Principle (OCP)</h3>
<p>The Open/Closed Principle dictates that software entities (classes, modules, functions) should be open for extension but closed for modification. This means that you should be able to add new features or behaviors without altering existing code. Think about software as a Lego masterpiece. When you want to add a shiny new block, the last thing you want is to disassemble the entire structure. OCP is the guardian of extensibility. Discipline on this requires that your code in a way that welcomes new features without sending shockwaves through the old ones.</p>
<h3 id="3-liskov-substitution-principle-lsp">3. Liskov Substitution Principle (LSP)</h3>
<p>The Liskov Substitution Principle emphasizes that objects of a subclass should be able to replace objects of the parent class without affecting the correctness of the program. In other terms, subinstances should be usable interchangeably with parent instances without causing errors or unexpected behavior. Adhering to LSP ensures that the relationships between classes are well-defined and intuitive. If you&rsquo;re familiar with Smalltalk, you can think if it wouldn&rsquo;t be the case for senders of <code>self subclassResponsibility</code>.</p>
<h3 id="4-interface-segregation-principle-isp">4. Interface Segregation Principle (ISP)</h3>
<p>Ever been to a buffet where you&rsquo;re forced to eat everything? Not fun, right? In code, the same applies. The Interface Segregation Principle suggests that clients should not be forced to depend on interfaces they do not use. In essence, this means that you should create specific interfaces for specific client needs, rather than creating large, monolithic interfaces. By doing so, you prevent clients from being burdened with unnecessary methods, promoting cleaner code and more focused APIs.</p>
<h3 id="5-dependency-inversion-principle-dip">5. Dependency Inversion Principle (DIP)</h3>
<p>This one&rsquo;s like inviting your in-laws to manage your household for a change. Proposed by Robert C. Martin, this principle aims to create a more flexible, maintainable, and easily testable codebase by reversing the traditional flow of dependencies. In simpler terms, DIP suggests that business logic modules, should not directly depend on modules that handle implementation details and specifics. Instead, both should depend on abstractions that help them to coordinate the system functionality. This principle maintain the codebase more flexible and facilitates easier testing and maintenance.</p>
<p>In conclusion, the SOLID principles provide a set of guidelines that help software developers create maintainable, adaptable, and robust code. By adhering to these principles, you can write code that is easier to understand, modify, and extend, ultimately leading to more efficient development and reduced chances of introducing bugs. Whether you&rsquo;re a seasoned developer or just starting your programming journey, understanding and applying the SOLID principles can significantly elevate the quality of your code.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/conquerComplexity.jpg" alt=""" loading="lazy" /></figure><p>In the realm of software development, crafting code that remains maintainable, flexible, and scalable stands as an utmost priority. And when delving into the concept of scalability, I&rsquo;m considering not only the system&rsquo;s load capacity but also your ability to maintain it, essentially wielding it as a tool to conquer complexity. To achieve this, developers often embrace design principles that steer them in writing this type of high-quality code. One such set of principles is known as SOLID, an acronym representing five essential tenets in object-oriented programming.</p>
<p>Let&rsquo;s delve into what each of these principles entails:</p>
<h3 id="1-single-responsibility-principle-srp">1. Single Responsibility Principle (SRP)</h3>
<p>The Single Responsibility Principle emphasizes that a class should have only one reason to change. Intentionally resisting designs that would mix responsibilities. In other words, a class should always have only one responsibility. This principle encourages us to break down complex classes into smaller, focused ones, each responsible for a single task. By adhering to SRP, code becomes more modular and easier to maintain, as changes in one area of functionality are less likely to affect unrelated parts. Discipline on this will enable your team to architect a maintainable and flexible dependency tree.</p>
<h3 id="2-openclosed-principle-ocp">2. Open/Closed Principle (OCP)</h3>
<p>The Open/Closed Principle dictates that software entities (classes, modules, functions) should be open for extension but closed for modification. This means that you should be able to add new features or behaviors without altering existing code. Think about software as a Lego masterpiece. When you want to add a shiny new block, the last thing you want is to disassemble the entire structure. OCP is the guardian of extensibility. Discipline on this requires that your code in a way that welcomes new features without sending shockwaves through the old ones.</p>
<h3 id="3-liskov-substitution-principle-lsp">3. Liskov Substitution Principle (LSP)</h3>
<p>The Liskov Substitution Principle emphasizes that objects of a subclass should be able to replace objects of the parent class without affecting the correctness of the program. In other terms, subinstances should be usable interchangeably with parent instances without causing errors or unexpected behavior. Adhering to LSP ensures that the relationships between classes are well-defined and intuitive. If you&rsquo;re familiar with Smalltalk, you can think if it wouldn&rsquo;t be the case for senders of <code>self subclassResponsibility</code>.</p>
<h3 id="4-interface-segregation-principle-isp">4. Interface Segregation Principle (ISP)</h3>
<p>Ever been to a buffet where you&rsquo;re forced to eat everything? Not fun, right? In code, the same applies. The Interface Segregation Principle suggests that clients should not be forced to depend on interfaces they do not use. In essence, this means that you should create specific interfaces for specific client needs, rather than creating large, monolithic interfaces. By doing so, you prevent clients from being burdened with unnecessary methods, promoting cleaner code and more focused APIs.</p>
<h3 id="5-dependency-inversion-principle-dip">5. Dependency Inversion Principle (DIP)</h3>
<p>This one&rsquo;s like inviting your in-laws to manage your household for a change. Proposed by Robert C. Martin, this principle aims to create a more flexible, maintainable, and easily testable codebase by reversing the traditional flow of dependencies. In simpler terms, DIP suggests that business logic modules, should not directly depend on modules that handle implementation details and specifics. Instead, both should depend on abstractions that help them to coordinate the system functionality. This principle maintain the codebase more flexible and facilitates easier testing and maintenance.</p>
<p>In conclusion, the SOLID principles provide a set of guidelines that help software developers create maintainable, adaptable, and robust code. By adhering to these principles, you can write code that is easier to understand, modify, and extend, ultimately leading to more efficient development and reduced chances of introducing bugs. Whether you&rsquo;re a seasoned developer or just starting your programming journey, understanding and applying the SOLID principles can significantly elevate the quality of your code.</p>
]]></content:encoded>
    </item>
    <item>
      <title>A Simplified Guide to Creating a Pharo Smalltalk Plugin</title>
      <link>https://blog.sebastiansastre.co/posts/a-simplified-guide-to-creating-a-pharo-smalltalk-plugin/</link>
      <pubDate>Fri, 17 Mar 2023 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/a-simplified-guide-to-creating-a-pharo-smalltalk-plugin/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/StarterPlugin.jpg" type="image/jpeg" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/StarterPlugin.jpg" alt=""" loading="lazy" /></figure><p>In my previous article <a href="https://blog.sebastiansastre.co/posts/how-to-create-a-pharo-smalltalk-plugin/">How to Create a Pharo Smalltalk Plugin</a>, I outlined how to create a Pharo Smalltalk plugin. However, since then, I&rsquo;ve discovered a more convenient way to produce these plugins using the <a href="https://github.com/sebastianconcept/PharoPluginBuilder">PharoPluginBuilder</a> and <a href="https://github.com/sebastianconcept/StarterPlugin">StarterPlugin</a>. In this post, I wanted to inform and describe the idea of this simplified process of creating a Pharo Smalltalk plugin.</p>
<p>I&rsquo;ve run it several times in macOS and these steps work fine. I&rsquo;ll appreciate feedback on how it goes for other platforms which should work but I didn&rsquo;t find the time to try.</p>
<p>Using the new simplified procedure of <a href="https://github.com/sebastianconcept/PharoPluginBuilder">PharoPluginBuilder</a>, includes:</p>
<ol>
<li>The clone of the PharoPluginBuilder repo.</li>
<li>Having an <code>ide/</code> dir where you have a Pharo image meant for being the IDE to develop your plugin.</li>
<li>Having a <code>dependencies/</code> dir where this setup will sit A) a <code>pharo-vm</code> project clone that is required and B) any third party library that you would use.</li>
<li>The <code>dist/build</code> directory where you will find the binary built artifact of your plugin.</li>
</ol>
<p>Follow that README.md and get your own:</p>
<p><img loading="lazy" src="https://blog.sebastiansastre.co/img/testingStarterPlugin.gif"></p>
<h3 id="further-simplification">Further simplification</h3>
<p>Guillermo Polito has been experimenting with further simplifications and build conveniences in this branch <a href="https://github.com/guillep/pharo-vm/tree/feat/pluginbuilder">https://github.com/guillep/pharo-vm/tree/feat/pluginbuilder</a> which would allow to use it like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Configure cmake</span>
</span></span><span class="line"><span class="cl">cmake -B build -S repo/path
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate the vmmaker image with the plugin code</span>
</span></span><span class="line"><span class="cl">cmake --build build --target StarterPlugin_IMAGE
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate only the sources</span>
</span></span><span class="line"><span class="cl">cmake --build build --target StarterPlugin_generate_source
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Build the plugin</span>
</span></span><span class="line"><span class="cl">cmake --build buildv2 --target StarterPlugin
</span></span></code></pre></div><p>In conclusion, creating a Pharo Smalltalk plugin doesn&rsquo;t have to be a complex process. While there is space for improving this further, by using the PharoPluginBuilder and StarterPlugin, you can quickly and easily create a plugin that extends the functionality of the Smalltalk language. Follow the steps outlined in this post, and you&rsquo;ll be on your way to creating your own custom plugin in no time.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/StarterPlugin.jpg" alt=""" loading="lazy" /></figure><p>In my previous article <a href="https://blog.sebastiansastre.co/posts/how-to-create-a-pharo-smalltalk-plugin/">How to Create a Pharo Smalltalk Plugin</a>, I outlined how to create a Pharo Smalltalk plugin. However, since then, I&rsquo;ve discovered a more convenient way to produce these plugins using the <a href="https://github.com/sebastianconcept/PharoPluginBuilder">PharoPluginBuilder</a> and <a href="https://github.com/sebastianconcept/StarterPlugin">StarterPlugin</a>. In this post, I wanted to inform and describe the idea of this simplified process of creating a Pharo Smalltalk plugin.</p>
<p>I&rsquo;ve run it several times in macOS and these steps work fine. I&rsquo;ll appreciate feedback on how it goes for other platforms which should work but I didn&rsquo;t find the time to try.</p>
<p>Using the new simplified procedure of <a href="https://github.com/sebastianconcept/PharoPluginBuilder">PharoPluginBuilder</a>, includes:</p>
<ol>
<li>The clone of the PharoPluginBuilder repo.</li>
<li>Having an <code>ide/</code> dir where you have a Pharo image meant for being the IDE to develop your plugin.</li>
<li>Having a <code>dependencies/</code> dir where this setup will sit A) a <code>pharo-vm</code> project clone that is required and B) any third party library that you would use.</li>
<li>The <code>dist/build</code> directory where you will find the binary built artifact of your plugin.</li>
</ol>
<p>Follow that README.md and get your own:</p>
<p><img loading="lazy" src="https://blog.sebastiansastre.co/img/testingStarterPlugin.gif"></p>
<h3 id="further-simplification">Further simplification</h3>
<p>Guillermo Polito has been experimenting with further simplifications and build conveniences in this branch <a href="https://github.com/guillep/pharo-vm/tree/feat/pluginbuilder">https://github.com/guillep/pharo-vm/tree/feat/pluginbuilder</a> which would allow to use it like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Configure cmake</span>
</span></span><span class="line"><span class="cl">cmake -B build -S repo/path
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate the vmmaker image with the plugin code</span>
</span></span><span class="line"><span class="cl">cmake --build build --target StarterPlugin_IMAGE
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate only the sources</span>
</span></span><span class="line"><span class="cl">cmake --build build --target StarterPlugin_generate_source
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Build the plugin</span>
</span></span><span class="line"><span class="cl">cmake --build buildv2 --target StarterPlugin
</span></span></code></pre></div><p>In conclusion, creating a Pharo Smalltalk plugin doesn&rsquo;t have to be a complex process. While there is space for improving this further, by using the PharoPluginBuilder and StarterPlugin, you can quickly and easily create a plugin that extends the functionality of the Smalltalk language. Follow the steps outlined in this post, and you&rsquo;ll be on your way to creating your own custom plugin in no time.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Pharometer on TLIG</title>
      <link>https://blog.sebastiansastre.co/posts/pharometer-on-tlig/</link>
      <pubDate>Sun, 12 Mar 2023 20:55:04 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/pharometer-on-tlig/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/pharometer.jpg" type="image/jpeg" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/pharometer.jpg" alt=""" loading="lazy" /></figure><p>As businesses increasingly rely on software to deliver their products and services, ensuring that their production applications are running smoothly and efficiently becomes a must for maintaining what makes your app to be dependable on: application reliability, performance, and availability.</p>
<p>One critical aspect of monitoring production apps is measuring resource usage, including CPU, memory, and network utilization and that&rsquo;s very well covered by many solutions. As previously mentioned, I particularly like <a href="https://blog.sebastiansastre.co/article/the-tlig-stack">The TLIG Stack</a> for that. These metrics can provide valuable insights into the application&rsquo;s health, allowing teams to identify performance bottlenecks and detect potential issues before they escalate into larger problems.</p>
<p>But what about when we want to not be blind about how resources are used by every instance of an app running on Pharo Smalltalk virtual machines?</p>
<p>When it comes to monitoring this, from the operations point of view, your Pharo images need to not be black boxes anymore. You need to have <em>inner</em> visibility on how the computing resources are used by your app in each of these VMs.</p>
<p>Enter <a href="https://github.com/sebastianconcept/Pharometer">Pharometer</a>, a <a href="https://github.com/svenvc/zinc">Zinc</a> delegate for sampling Pharo metrics in a InfluxDB friendly format.</p>
<p>Pharo Smalltalk is a powerful and dynamic programming language that offers significant flexibility hence performance to grow features, but this can make it challenging to operate as you deploy versions because its resource usage could vary without you having a quick feedback loop about it. This creates a feedback gap. By monitoring the internal resource usage of every Pharo Smalltalk Virtual Machine, teams would close this feedback gap and more easily identify and resolve service performance issues, ensuring that their production applications remain fast, reliable, and available to users as any dependable service would.</p>
<p>When searching for what was already existing, I came across this nice blog post from <a href="https://github.com/PierceNg">Pierce Ng</a> <a href="https://samadhiweb.com/blog/2019.07.03.telemon.html">Telemon: Pharo metrics for Telegraf</a> showing the idea of using <a href="https://samadhiweb.com/blog/2019.06.13.tig.html">TIG</a> for collecting and displaying Pharo Smalltalk apps&rsquo; metrics.</p>
<p>Pierce&rsquo;s article got me encouraged to do my own TIG setup, thank you Pierce! Although it was great at the beginning, I started to feel I needed to add a couple of missing parts for using it to operate production services: logs aggregation and querying and availability monitoring and alerts. After researching options, I&rsquo;ve settled with Loki (fed by Promtail) and Kuma.</p>
<p>By the way, here you can see more on how to use <code>docker-compose</code> to have all that running in a server <a href="https://blog.sebastiansastre.co/article/the-tlig-stack">The TLIG Stack</a>.</p>
<h4 id="open-the-hood">Open the hood</h4>
<p>Once the basic monitoring with the TLIG stack is available, we can go to the next level and add a nice Grafana dashboard displaying what we can collect using <a href="https://github.com/sebastianconcept/Pharometer">Pharometer</a> to see the performance details of running Pharo images.</p>
<p>You need two things for that to happen:</p>
<ol>
<li><strong>Pharo images with your app responding values</strong>. This is solved using a customized <code>Pharometer</code> in your running image.</li>
<li><strong>Telegraf configured to periodically sample these values</strong>. This is satisfied by configuring a custom HTTP plugin in Telegraf.</li>
</ol>
<p><code>Pharometer</code> is a single class program designed to work as a <code>Zinc</code> delegate. It has a <code>name</code>, <code>tags</code> for labelling, <code>fields</code> (that you can think as label and measured value pairs), and a <code>context</code> that can be used to optimize &ldquo;costly&rdquo; calculations to aid during the rendering process. Is this <code>context</code> dictionary, the place where you can store a value that you can re-use in calculating the metric of more than one <code>field</code> without having to compute the costly value more than once.</p>
<p>Let&rsquo;s see an example to understand its convenience.</p>
<p>Here, monitor <code>Socket</code> instances, we&rsquo;re adding one context builder to count all the instances of the <code>Socket</code> class, which we can consider as a costly operation we want to do once:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="o">|</span><span class="nv"> pharometer </span><span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="o">:=</span> <span class="nc">Pharometer</span> <span class="nf">named:</span> <span class="s">&#39;pharo&#39;</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="nf">addContextBuilder:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span> <span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#sockets</span> <span class="nf">put:</span> <span class="nc">Socket</span> <span class="nf">allInstances</span> ]<span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="nf">addTag:</span> <span class="ss">#app</span> <span class="nf">value:</span> <span class="s">&#39;app-42&#39;</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="nf">addTag:</span> <span class="ss">#image</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">image</span> <span class="nf">imageDirectory</span> <span class="nf">pathString</span> ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">pharometer</span>
</span></span><span class="line"><span class="cl">  <span class="err">addField:</span> <span class="ss">#totalSockets</span>
</span></span><span class="line"><span class="cl">  <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#34;Quantity of all Socket instances&#34;</span>
</span></span><span class="line"><span class="cl">    (<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#sockets</span>) <span class="nf">size</span>  ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">pharometer</span>
</span></span><span class="line"><span class="cl">  <span class="err">addField:</span> <span class="ss">#connectedSockets</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#34;From all Socket instances, quantity of those which are connected&#34;</span>
</span></span><span class="line"><span class="cl">    ((<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#sockets</span>) <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">isConnected</span> ]) <span class="nf">size</span> ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">pharometer</span>
</span></span><span class="line"><span class="cl">  <span class="err">addField:</span> <span class="ss">#validSockets</span>
</span></span><span class="line"><span class="cl">  <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#34;From all Socket instances, quantity of those which are in a valid state&#34;</span>
</span></span><span class="line"><span class="cl">    ((<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#sockets</span>) <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">isValid</span> ]) <span class="nf">size</span> ]<span class="p">.</span>
</span></span></code></pre></div><p>The <code>render</code> method will create a new context by sequentially evaluating all the <code>contextBuilder</code> closures that you added (if any).</p>
<p>The previous example would grab all the current instances of Socket once and render these 3 measurements derived from it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="nf">render</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="c">&#34;&#39;pharo,app=app-42,image=/Users/seb/Developer/blah-project totalSockets=14,connectedSockets=4,validSockets=12&#39;&#34;</span>
</span></span></code></pre></div><p><code>Pharometer</code> is clean and doesn&rsquo;t have any default values or tags. And yet, is flexible enough to be customized to serve whatever fresh measurements you make it calculate. All by efficiently consuming them during the rendering phase when the field closures get executed.</p>
<p>Although it&rsquo;s clean, I&rsquo;ve added some convenient <code>presets</code> methods that will add different categories of measurements. At least I wanted a way to keep an eye on sockets, memory, threads and &quot;other&quot; resources.</p>
<p>Note that, while <code>addMemoryMetrics</code> doesn&rsquo;t make any use of the context builder:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">addMemoryMetrics</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="bp">self</span>
</span></span><span class="line"><span class="cl">      <span class="nf">addField:</span> <span class="ss">#freeSize</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">freeSize</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">addField:</span> <span class="ss">#edenSpaceSize</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">edenSpaceSize</span> ]<span class="p">;</span><span class="nf">addField:</span> <span class="ss">#youngSpaceSize</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">youngSpaceSize</span> ]<span class="p">;</span><span class="nf">addField:</span> <span class="ss">#freeOldSpaceSize</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">freeOldSpaceSize</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">addField:</span> <span class="ss">#memorySize</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">memorySize</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">yourself</span><span class="p">.</span>
</span></span></code></pre></div><p><code>addThreadMetrics</code> makes a convenient use of it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">addThreadMetrics</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span>
</span></span><span class="line"><span class="cl">        <span class="nf">addContextBuilder:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#processes</span> <span class="nf">put:</span> <span class="nc">Process</span> <span class="nf">allInstances</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">addField:</span> <span class="ss">#totalProcesses</span>
</span></span><span class="line"><span class="cl">        <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span> (<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#processes</span>) <span class="nf">size</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">addField:</span> <span class="ss">#suspendedProcesses</span>
</span></span><span class="line"><span class="cl">        <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          ((<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#processes</span>) <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">isSuspended</span> ]) <span class="nf">size</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">addField:</span> <span class="ss">#terminatedProcesses</span>
</span></span><span class="line"><span class="cl">        <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          ((<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#processes</span>) <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">isTerminated</span> ]) <span class="nf">size</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">addField:</span> <span class="ss">#terminatingProcesses</span>
</span></span><span class="line"><span class="cl">        <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          ((<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#processes</span>) <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">isTerminating</span> ]) <span class="nf">size</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">yourself</span>
</span></span></code></pre></div><h4 id="putting-it-all-together">Putting it all together</h4>
<p>If you don&rsquo;t have yet custom metrics you want to sample, here is a handy setup that will measure basic VM resources usage for you:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="o">|</span><span class="nv"> pharometer zincServer </span><span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="o">:=</span> (<span class="nc">Pharometer</span> <span class="nf">named:</span> <span class="s">&#39;pharo42&#39;</span>)
</span></span><span class="line"><span class="cl">  <span class="nf">addMemoryMetrics</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">addSocketMetrics</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">addThreadMetrics</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">addOtherMetrics</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">yourself</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">  <span class="nv">zincServer</span> <span class="o">:=</span> <span class="nc">ZnServer</span> <span class="nf">on:</span> <span class="m">8890</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">  <span class="nv">zincServer</span> <span class="nf">delegate:</span> <span class="nv">pharometer</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">  <span class="nv">zincServer</span> <span class="nf">start</span><span class="p">.</span>
</span></span></code></pre></div><p>With this as part of you app build and startup process, all that is left is to consume it. When using the TLIG stack, this is done with a generic Telegraf HTTP plugin.</p>
<p>If you use <a href="https://blog.sebastiansastre.co/article/the-tlig-stack">the TLIG stack</a> from this repository, you&rsquo;ll find a <code>conf/telegraf/telegraf.conf</code> file that is a massive yml file.</p>
<p>To add the plugin that would sample your <code>Pharometer</code> measurments, search for <code>Read formatted Pharo metrics from an HTTP endpoint</code> and all you need to do there is to tell, in which URL, Pharometer is rendering that output</p>
<p>In this example, it&rsquo;s using <code>http://host.docker.internal:8890\&quot;</code> because that TLIG stack runs using <code>docker-compose</code> and <code>host.docker.internal</code> is a way to make it find when your Pharo app is reachable from the host. Have in mind this good for a starter setup but this will vary depending on how you deploy and how complex your deployed targets are.</p>
<p>Here is a sample of a Telegraf working HTTP plugin:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># Read formatted Pharo metrics from an HTTP endpoint</span>
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">inputs</span><span class="p">.</span><span class="nx">http</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="c">## One or more URLs from which to read formatted metrics</span>
</span></span><span class="line"><span class="cl"><span class="nx">urls</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;http://host.docker.internal:8890&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c">## HTTP method</span>
</span></span><span class="line"><span class="cl"><span class="nx">method</span> <span class="p">=</span> <span class="s2">&#34;GET&#34;</span>
</span></span></code></pre></div><p>A pending follow up to this setup is find a way to conveniently setup Telegraf to monitor a bunch of Pharo Smalltalk running images and make a overview dashboard for a cluster and a per-Pharo dashboard when you want to drill down and check its performance details.</p>
<p>I hope that by now you have a better sense of how to monitor services that are built on top of Pharo Smalltalk applications in production and that Pharometer, as a Zinc delegate, can provide valuable insights into the application&rsquo;s health by measuring resource usage.</p>
<p>Looking forward to see people sharing Grafana dashboards customized and useful to other people running Pharo based services.</p>
<p>Have a happy Grafana dashboarding process based on TLIG and Pharometer!</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/pharometer.jpg" alt=""" loading="lazy" /></figure><p>As businesses increasingly rely on software to deliver their products and services, ensuring that their production applications are running smoothly and efficiently becomes a must for maintaining what makes your app to be dependable on: application reliability, performance, and availability.</p>
<p>One critical aspect of monitoring production apps is measuring resource usage, including CPU, memory, and network utilization and that&rsquo;s very well covered by many solutions. As previously mentioned, I particularly like <a href="https://blog.sebastiansastre.co/article/the-tlig-stack">The TLIG Stack</a> for that. These metrics can provide valuable insights into the application&rsquo;s health, allowing teams to identify performance bottlenecks and detect potential issues before they escalate into larger problems.</p>
<p>But what about when we want to not be blind about how resources are used by every instance of an app running on Pharo Smalltalk virtual machines?</p>
<p>When it comes to monitoring this, from the operations point of view, your Pharo images need to not be black boxes anymore. You need to have <em>inner</em> visibility on how the computing resources are used by your app in each of these VMs.</p>
<p>Enter <a href="https://github.com/sebastianconcept/Pharometer">Pharometer</a>, a <a href="https://github.com/svenvc/zinc">Zinc</a> delegate for sampling Pharo metrics in a InfluxDB friendly format.</p>
<p>Pharo Smalltalk is a powerful and dynamic programming language that offers significant flexibility hence performance to grow features, but this can make it challenging to operate as you deploy versions because its resource usage could vary without you having a quick feedback loop about it. This creates a feedback gap. By monitoring the internal resource usage of every Pharo Smalltalk Virtual Machine, teams would close this feedback gap and more easily identify and resolve service performance issues, ensuring that their production applications remain fast, reliable, and available to users as any dependable service would.</p>
<p>When searching for what was already existing, I came across this nice blog post from <a href="https://github.com/PierceNg">Pierce Ng</a> <a href="https://samadhiweb.com/blog/2019.07.03.telemon.html">Telemon: Pharo metrics for Telegraf</a> showing the idea of using <a href="https://samadhiweb.com/blog/2019.06.13.tig.html">TIG</a> for collecting and displaying Pharo Smalltalk apps&rsquo; metrics.</p>
<p>Pierce&rsquo;s article got me encouraged to do my own TIG setup, thank you Pierce! Although it was great at the beginning, I started to feel I needed to add a couple of missing parts for using it to operate production services: logs aggregation and querying and availability monitoring and alerts. After researching options, I&rsquo;ve settled with Loki (fed by Promtail) and Kuma.</p>
<p>By the way, here you can see more on how to use <code>docker-compose</code> to have all that running in a server <a href="https://blog.sebastiansastre.co/article/the-tlig-stack">The TLIG Stack</a>.</p>
<h4 id="open-the-hood">Open the hood</h4>
<p>Once the basic monitoring with the TLIG stack is available, we can go to the next level and add a nice Grafana dashboard displaying what we can collect using <a href="https://github.com/sebastianconcept/Pharometer">Pharometer</a> to see the performance details of running Pharo images.</p>
<p>You need two things for that to happen:</p>
<ol>
<li><strong>Pharo images with your app responding values</strong>. This is solved using a customized <code>Pharometer</code> in your running image.</li>
<li><strong>Telegraf configured to periodically sample these values</strong>. This is satisfied by configuring a custom HTTP plugin in Telegraf.</li>
</ol>
<p><code>Pharometer</code> is a single class program designed to work as a <code>Zinc</code> delegate. It has a <code>name</code>, <code>tags</code> for labelling, <code>fields</code> (that you can think as label and measured value pairs), and a <code>context</code> that can be used to optimize &ldquo;costly&rdquo; calculations to aid during the rendering process. Is this <code>context</code> dictionary, the place where you can store a value that you can re-use in calculating the metric of more than one <code>field</code> without having to compute the costly value more than once.</p>
<p>Let&rsquo;s see an example to understand its convenience.</p>
<p>Here, monitor <code>Socket</code> instances, we&rsquo;re adding one context builder to count all the instances of the <code>Socket</code> class, which we can consider as a costly operation we want to do once:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="o">|</span><span class="nv"> pharometer </span><span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="o">:=</span> <span class="nc">Pharometer</span> <span class="nf">named:</span> <span class="s">&#39;pharo&#39;</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="nf">addContextBuilder:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span> <span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#sockets</span> <span class="nf">put:</span> <span class="nc">Socket</span> <span class="nf">allInstances</span> ]<span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="nf">addTag:</span> <span class="ss">#app</span> <span class="nf">value:</span> <span class="s">&#39;app-42&#39;</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="nf">addTag:</span> <span class="ss">#image</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">image</span> <span class="nf">imageDirectory</span> <span class="nf">pathString</span> ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">pharometer</span>
</span></span><span class="line"><span class="cl">  <span class="err">addField:</span> <span class="ss">#totalSockets</span>
</span></span><span class="line"><span class="cl">  <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#34;Quantity of all Socket instances&#34;</span>
</span></span><span class="line"><span class="cl">    (<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#sockets</span>) <span class="nf">size</span>  ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">pharometer</span>
</span></span><span class="line"><span class="cl">  <span class="err">addField:</span> <span class="ss">#connectedSockets</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#34;From all Socket instances, quantity of those which are connected&#34;</span>
</span></span><span class="line"><span class="cl">    ((<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#sockets</span>) <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">isConnected</span> ]) <span class="nf">size</span> ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">pharometer</span>
</span></span><span class="line"><span class="cl">  <span class="err">addField:</span> <span class="ss">#validSockets</span>
</span></span><span class="line"><span class="cl">  <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#34;From all Socket instances, quantity of those which are in a valid state&#34;</span>
</span></span><span class="line"><span class="cl">    ((<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#sockets</span>) <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">isValid</span> ]) <span class="nf">size</span> ]<span class="p">.</span>
</span></span></code></pre></div><p>The <code>render</code> method will create a new context by sequentially evaluating all the <code>contextBuilder</code> closures that you added (if any).</p>
<p>The previous example would grab all the current instances of Socket once and render these 3 measurements derived from it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="nf">render</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="c">&#34;&#39;pharo,app=app-42,image=/Users/seb/Developer/blah-project totalSockets=14,connectedSockets=4,validSockets=12&#39;&#34;</span>
</span></span></code></pre></div><p><code>Pharometer</code> is clean and doesn&rsquo;t have any default values or tags. And yet, is flexible enough to be customized to serve whatever fresh measurements you make it calculate. All by efficiently consuming them during the rendering phase when the field closures get executed.</p>
<p>Although it&rsquo;s clean, I&rsquo;ve added some convenient <code>presets</code> methods that will add different categories of measurements. At least I wanted a way to keep an eye on sockets, memory, threads and &quot;other&quot; resources.</p>
<p>Note that, while <code>addMemoryMetrics</code> doesn&rsquo;t make any use of the context builder:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">addMemoryMetrics</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="bp">self</span>
</span></span><span class="line"><span class="cl">      <span class="nf">addField:</span> <span class="ss">#freeSize</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">freeSize</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">addField:</span> <span class="ss">#edenSpaceSize</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">edenSpaceSize</span> ]<span class="p">;</span><span class="nf">addField:</span> <span class="ss">#youngSpaceSize</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">youngSpaceSize</span> ]<span class="p">;</span><span class="nf">addField:</span> <span class="ss">#freeOldSpaceSize</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">freeOldSpaceSize</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">addField:</span> <span class="ss">#memorySize</span> <span class="nf">value:</span> [ <span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">memorySize</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">yourself</span><span class="p">.</span>
</span></span></code></pre></div><p><code>addThreadMetrics</code> makes a convenient use of it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">addThreadMetrics</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span>
</span></span><span class="line"><span class="cl">        <span class="nf">addContextBuilder:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#processes</span> <span class="nf">put:</span> <span class="nc">Process</span> <span class="nf">allInstances</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">addField:</span> <span class="ss">#totalProcesses</span>
</span></span><span class="line"><span class="cl">        <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span> (<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#processes</span>) <span class="nf">size</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">addField:</span> <span class="ss">#suspendedProcesses</span>
</span></span><span class="line"><span class="cl">        <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          ((<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#processes</span>) <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">isSuspended</span> ]) <span class="nf">size</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">addField:</span> <span class="ss">#terminatedProcesses</span>
</span></span><span class="line"><span class="cl">        <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          ((<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#processes</span>) <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">isTerminated</span> ]) <span class="nf">size</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">addField:</span> <span class="ss">#terminatingProcesses</span>
</span></span><span class="line"><span class="cl">        <span class="nf">value:</span> [ <span class="o">:</span><span class="nv">ctx</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          ((<span class="nv">ctx</span> <span class="nf">at:</span> <span class="ss">#processes</span>) <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">isTerminating</span> ]) <span class="nf">size</span> ]<span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">yourself</span>
</span></span></code></pre></div><h4 id="putting-it-all-together">Putting it all together</h4>
<p>If you don&rsquo;t have yet custom metrics you want to sample, here is a handy setup that will measure basic VM resources usage for you:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="o">|</span><span class="nv"> pharometer zincServer </span><span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="nv">pharometer</span> <span class="o">:=</span> (<span class="nc">Pharometer</span> <span class="nf">named:</span> <span class="s">&#39;pharo42&#39;</span>)
</span></span><span class="line"><span class="cl">  <span class="nf">addMemoryMetrics</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">addSocketMetrics</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">addThreadMetrics</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">addOtherMetrics</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">yourself</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">  <span class="nv">zincServer</span> <span class="o">:=</span> <span class="nc">ZnServer</span> <span class="nf">on:</span> <span class="m">8890</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">  <span class="nv">zincServer</span> <span class="nf">delegate:</span> <span class="nv">pharometer</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">  <span class="nv">zincServer</span> <span class="nf">start</span><span class="p">.</span>
</span></span></code></pre></div><p>With this as part of you app build and startup process, all that is left is to consume it. When using the TLIG stack, this is done with a generic Telegraf HTTP plugin.</p>
<p>If you use <a href="https://blog.sebastiansastre.co/article/the-tlig-stack">the TLIG stack</a> from this repository, you&rsquo;ll find a <code>conf/telegraf/telegraf.conf</code> file that is a massive yml file.</p>
<p>To add the plugin that would sample your <code>Pharometer</code> measurments, search for <code>Read formatted Pharo metrics from an HTTP endpoint</code> and all you need to do there is to tell, in which URL, Pharometer is rendering that output</p>
<p>In this example, it&rsquo;s using <code>http://host.docker.internal:8890\&quot;</code> because that TLIG stack runs using <code>docker-compose</code> and <code>host.docker.internal</code> is a way to make it find when your Pharo app is reachable from the host. Have in mind this good for a starter setup but this will vary depending on how you deploy and how complex your deployed targets are.</p>
<p>Here is a sample of a Telegraf working HTTP plugin:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># Read formatted Pharo metrics from an HTTP endpoint</span>
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">inputs</span><span class="p">.</span><span class="nx">http</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="c">## One or more URLs from which to read formatted metrics</span>
</span></span><span class="line"><span class="cl"><span class="nx">urls</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;http://host.docker.internal:8890&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c">## HTTP method</span>
</span></span><span class="line"><span class="cl"><span class="nx">method</span> <span class="p">=</span> <span class="s2">&#34;GET&#34;</span>
</span></span></code></pre></div><p>A pending follow up to this setup is find a way to conveniently setup Telegraf to monitor a bunch of Pharo Smalltalk running images and make a overview dashboard for a cluster and a per-Pharo dashboard when you want to drill down and check its performance details.</p>
<p>I hope that by now you have a better sense of how to monitor services that are built on top of Pharo Smalltalk applications in production and that Pharometer, as a Zinc delegate, can provide valuable insights into the application&rsquo;s health by measuring resource usage.</p>
<p>Looking forward to see people sharing Grafana dashboards customized and useful to other people running Pharo based services.</p>
<p>Have a happy Grafana dashboarding process based on TLIG and Pharometer!</p>
]]></content:encoded>
    </item>
    <item>
      <title>One step closer to a TensorFlow Pharo Plugin</title>
      <link>https://blog.sebastiansastre.co/posts/one-step-closer-to-a-tensorflow-pharo-plugin/</link>
      <pubDate>Thu, 09 Mar 2023 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/one-step-closer-to-a-tensorflow-pharo-plugin/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/workingOnTech.jpg" type="image/jpeg" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/workingOnTech.jpg" alt=""" loading="lazy" /></figure><p>I&rsquo;m excited to announce that the <a href="https://pharo.org/news/2023-02-24-GSOC23Accepted.html">Pharo Consortium has been approved as an organizer for Google Summer of Code 2023</a>! As a mentor for the Pharo TensorFlow plugin project, I&rsquo;m thrilled to be a part of this program and to help developers to work on a project for the Pharo community.</p>
<p>Google Summer of Code is an annual program that brings together student developers from around the world to work on open source projects with experienced mentors. As in previous years, this is a great opportunity for students to gain practical experience in software development using Pharo Smalltalk and doing so by contributing to real-world projects that have an impact on the whole open source community.</p>
<p>The project I&rsquo;m going to help with is the one we proposed last february here, <a href="https://blog.sebastiansastre.co/posts/bringing-tensorflow-to-pharo/">Bringing TensorFlow to Pharo</a>.</p>
<p>The Pharo TensorFlow plugin project aims to integrate TensorFlow, a popular machine learning library, with the Pharo programming language. This will enable Pharo developers to efficiently incorporate high-performing computations that are typical in a hot segment of the industry right now: machine learning and everything AI. And with a Pharo TensorFlow Plugin, the capabilities of the Pharo applications are not going to be limited to the CPU anymore. They will start to be able to yield GPU power to execute all the powerful features and algebraic conveniences that TensorFlow provides.</p>
<p>Together with Sebastian Jordan, we&rsquo;ll be mentoring this project to work closely with student developers and guide them through the development process, answer their questions, and help them overcome the challenges that they encounter along the way. I&rsquo;m excited to see what they will achieve and to help them make a meaningful contribution to the Pharo community.</p>
<p>If you&rsquo;re a student developer who is interested in participating in <a href="https://gsoc.pharo.org/ideas">Google Summer of Code 2023</a>, we encourage you to check out the program website and explore the various projects that are available. And if you&rsquo;re interested in the Pharo TensorFlow plugin project, we&rsquo;d love to hear from you! Feel free to reach out to us to learn more and to get involved.</p>
<p>We look forward to a productive and exciting <a href="https://summerofcode.withgoogle.com/">Google Summer of Code 2023</a>, and we can&rsquo;t wait to see what the future holds for the Pharo community and the open source projects that we&rsquo;re working on. Stay tuned for more updates and progress reports throughout the summer!</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/workingOnTech.jpg" alt=""" loading="lazy" /></figure><p>I&rsquo;m excited to announce that the <a href="https://pharo.org/news/2023-02-24-GSOC23Accepted.html">Pharo Consortium has been approved as an organizer for Google Summer of Code 2023</a>! As a mentor for the Pharo TensorFlow plugin project, I&rsquo;m thrilled to be a part of this program and to help developers to work on a project for the Pharo community.</p>
<p>Google Summer of Code is an annual program that brings together student developers from around the world to work on open source projects with experienced mentors. As in previous years, this is a great opportunity for students to gain practical experience in software development using Pharo Smalltalk and doing so by contributing to real-world projects that have an impact on the whole open source community.</p>
<p>The project I&rsquo;m going to help with is the one we proposed last february here, <a href="https://blog.sebastiansastre.co/posts/bringing-tensorflow-to-pharo/">Bringing TensorFlow to Pharo</a>.</p>
<p>The Pharo TensorFlow plugin project aims to integrate TensorFlow, a popular machine learning library, with the Pharo programming language. This will enable Pharo developers to efficiently incorporate high-performing computations that are typical in a hot segment of the industry right now: machine learning and everything AI. And with a Pharo TensorFlow Plugin, the capabilities of the Pharo applications are not going to be limited to the CPU anymore. They will start to be able to yield GPU power to execute all the powerful features and algebraic conveniences that TensorFlow provides.</p>
<p>Together with Sebastian Jordan, we&rsquo;ll be mentoring this project to work closely with student developers and guide them through the development process, answer their questions, and help them overcome the challenges that they encounter along the way. I&rsquo;m excited to see what they will achieve and to help them make a meaningful contribution to the Pharo community.</p>
<p>If you&rsquo;re a student developer who is interested in participating in <a href="https://gsoc.pharo.org/ideas">Google Summer of Code 2023</a>, we encourage you to check out the program website and explore the various projects that are available. And if you&rsquo;re interested in the Pharo TensorFlow plugin project, we&rsquo;d love to hear from you! Feel free to reach out to us to learn more and to get involved.</p>
<p>We look forward to a productive and exciting <a href="https://summerofcode.withgoogle.com/">Google Summer of Code 2023</a>, and we can&rsquo;t wait to see what the future holds for the Pharo community and the open source projects that we&rsquo;re working on. Stay tuned for more updates and progress reports throughout the summer!</p>
]]></content:encoded>
    </item>
    <item>
      <title>The TLIG stack</title>
      <link>https://blog.sebastiansastre.co/posts/the-tlig-stack/</link>
      <pubDate>Tue, 07 Mar 2023 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/the-tlig-stack/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/grafana1.png" type="image/png" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/grafana1.png" alt=""" loading="lazy" /></figure><p>Monitoring is an essential part of any modern infrastructure. It helps you keep an eye on system performance, detect anomalies, and identify potential issues before they become critical. With the right tools, monitoring can be a straightforward process that provides valuable insights into your system&rsquo;s health.</p>
<p>However, if you&rsquo;re like me, you can feel that setting up an effective monitoring system today can be daunting, especially for those who are new to the field. There are a plethora of tools and technologies available, and it can be difficult to know where to start.</p>
<p>In this article, I will show you how to set up Promtail, Telegraf, InfluxDB, Loki, and Grafana to monitor your system&rsquo;s performance and logs plus Kuma for service availability and alerts. I&rsquo;ve selected this toolchain because is reasonably straightforward to setup, shows good potential and flexibility for further growing needs.</p>
<p>And I&rsquo;ve created this <code>docker-compose</code> setup <a href="https://github.com/sebastianconcept/tlig:v1.0.2">TLIG v1.0.2</a> so you can have a reference to use as a starter.</p>
<p>By the end of this article, you will have a good understanding of how to set up a monitoring system for your infrastructure, and an idea on how you&rsquo;ll be able to customize it to meet some of your specific needs.</p>
<h4 id="what-to-monitor">What to Monitor</h4>
<p>With a system&rsquo;s design approach, we can identify the critical components of your infrastructure and then set up monitoring for those components so you have the right monitoring at the right levels. Any component in your service tech stack that is sensible to computing resources should be monitored for performance.</p>
<p>Sometimes a component could go down and the service would still be able to operate in a degraded state. Sometimes it won&rsquo;t be able to and you need to receive an adequate alarm pointing to that.</p>
<p>Let&rsquo;s review their categories:</p>
<p><strong>Infrastructure</strong>. Components can include servers, network devices, and storage systems. It&rsquo;s essential to monitor the health and performance of these components to detect issues that may lead to downtime or data loss. Kuma is going to help with this.</p>
<p><strong>Application Services</strong>. These are the backbone of any system. Monitoring the performance of the backend service is essential to ensure that requests are being processed efficiently and without errors. Telegraf will help here by efficiently collecting periodic measures as instructed in its customizable configuration and plugins.  The availability status is going to be covered by Kuma and its performance will be displayed in Grafana dashboards.</p>
<p><strong>Application Logs</strong>. Logs are the most obvious and expected way for headless services to leave a human readable trace of their activity for any system. They provide valuable information about what&rsquo;s happening in your application and have big value to be used to diagnose issues, identify trends, and even track user behavior. Loki will be used to have this accessible in one place and easy to query.</p>
<p><strong>Metrics</strong>. Metrics provide a high-level view of system performance over time. They help to identify trends and potential issues before they become critical. Metrics can include CPU usage, memory usage, disk space, network traffic and the usage of many other resources. Again, all of the performance details are going to be displayed in Grafana dashboards.</p>
<p>Let&rsquo;s take a moment to review each component and its function:</p>
<h4 id="promtail">Promtail</h4>
<p><a href="https://grafana.com/docs/loki/latest/clients/promtail/">Promtail</a> is a log collector and parser that sends logs to Loki. It can tail log files, filter logs based on patterns, and enrich log data with metadata. Promtail is designed to be highly scalable and can handle a large number of log streams.</p>
<h4 id="telegraf">Telegraf</h4>
<p><a href="https://github.com/influxdata/telegraf">Telegraf</a> is a plugin-driven server agent that can collect and send metrics to a variety of datastores. It supports a wide range of input plugins, including system metrics, network metrics, and Docker stats. Telegraf can also transform and aggregate metrics before sending them to the datastore.</p>
<h4 id="influxdb">InfluxDB</h4>
<p><a href="https://www.influxdata.com/">InfluxDB</a> is a time-series database that stores and queries time-stamped data. It provides a SQL-like query language for querying and aggregating data. InfluxDB is highly scalable and can handle millions of writes per second.</p>
<h4 id="loki">Loki</h4>
<p><a href="https://github.com/grafana/loki">Loki</a> is a horizontally scalable, highly available log aggregation system. It is designed to be a cost-effective way to handle large amounts of log data. Loki can ingest logs from various sources, including Promtail, and allows you to search and filter logs based on labels.</p>
<h4 id="grafana">Grafana</h4>
<p><a href="https://grafana.com/">Grafana</a> is a popular open-source platform for data visualization and monitoring. It can connect to various data sources, including InfluxDB and Loki, and provides an appealing web-based interface for creating and sharing dashboards. Grafana supports a wide range of visualization options, including charts, tables, and alerts.</p>
<p>Now that we have a good understanding of each component&rsquo;s role in the monitoring stack, let&rsquo;s move on to setting up the monitoring system using Docker Compose as found in  <a href="https://github.com/sebastianconcept/tlig:v1.0.2">TLIG v1.0.2</a>.</p>
<p><code>docker-compose.yml</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tlig</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">loki</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">grafana/loki:2.4.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./conf/loki:/etc/loki:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${DOCKER_LOKI_PORT}:3100      </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span>-<span class="l">config.file=/etc/loki/loki-config.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">promtail</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tlig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">promtail</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">grafana/promtail:2.4.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/log:/var/log:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./conf/promtail:/etc/promtail:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span>-<span class="l">config.file=/etc/promtail/promtail-config.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tlig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">influxdb</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">influxdb:2.1.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data/influxdb:/var/lib/influxdb2:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entrypoint</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;./entrypoint.sh&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="kc">on</span>-<span class="l">failure:10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${DOCKER_INFLUXDB_INIT_PORT}:8086</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tlig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">telegraf</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">telegraf:1.19</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${TELEGRAF_CFG_PATH}:/etc/telegraf/telegraf.conf:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">influxdb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tlig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">grafana</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">grafana/grafana-oss:8.4.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="l">${USER_ID}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data/grafana:/var/lib/grafana:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">telegraf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">loki</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${GRAFANA_PORT}:3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tlig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kuma</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">louislam/uptime-kuma:1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">kuma</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data/kuma:/app/data:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${KUMA_PORT}:3001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">security_opt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="kc">no</span>-<span class="l">new-privileges:true</span><span class="w">
</span></span></span></code></pre></div><p>Create a <code>.env</code> for yourself based in this <code>.env.sample</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_MODE</span><span class="o">=</span>setup
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## Environment variables used during the setup and operation of the stack</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Primary InfluxDB admin/superuser credentials</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_USERNAME</span><span class="o">=</span>admin
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Make sure this pwd is not too short so Influx doesn&#39;t reject it.</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_PASSWORD</span><span class="o">=</span>adminpwd1234
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate your own with `openssl rand -hex 32`</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_ADMIN_TOKEN</span><span class="o">=</span>blahblahblahrandomstringtoken
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Primary InfluxDB organization &amp; bucket definitions</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_ORG</span><span class="o">=</span>yourorganization
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_BUCKET</span><span class="o">=</span>telegraf 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Primary InfluxDB bucket retention period</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="c1"># NOTE: Valid units are nanoseconds (ns), microseconds(us), milliseconds (ms)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># seconds (s), minutes (m), hours (h), days (d), and weeks (w).</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_RETENTION</span><span class="o">=</span>4d
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># InfluxDB port &amp; hostname definitions</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_PORT</span><span class="o">=</span><span class="m">8086</span> 
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_HOST</span><span class="o">=</span>influxdb 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Telegraf configuration file</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1"># Will be mounted to container and used as telegraf configuration</span>
</span></span><span class="line"><span class="cl"><span class="nv">TELEGRAF_CFG_PATH</span><span class="o">=</span>./conf/telegraf/telegraf.conf
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Loki port definition</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_LOKI_PORT</span><span class="o">=</span><span class="m">3100</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_LOKI_GRPC_PORT</span><span class="o">=</span><span class="m">9096</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Promtail port definition</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_PROMTAIL_PORT</span><span class="o">=</span><span class="m">9080</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_PROMTAIL_GRPC_PORT</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Grafana port definition</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Grafana default uses 3000 but let&#39;s move that elsewere, since that&#39;s</span>
</span></span><span class="line"><span class="cl"><span class="c1"># a popular port for many developing setups.</span>
</span></span><span class="line"><span class="cl"><span class="nv">GRAFANA_PORT</span><span class="o">=</span><span class="m">3008</span>
</span></span><span class="line"><span class="cl"><span class="nv">USER_ID</span><span class="o">=</span>xxxx <span class="c1">#replace xxxx with what you have for `$(id -u)`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Kuma port definition</span>
</span></span><span class="line"><span class="cl"><span class="nv">KUMA_PORT</span><span class="o">=</span><span class="m">3009</span>
</span></span></code></pre></div><p>For running it in one host, you should be good by setting the username, password, token, organization name and bucket name for InfluxDB and all the ports and <code>USER_ID</code>.</p>
<p>You&rsquo;ll be able to further config Promtail, Loki and Telegraf editing the files in <code>./conf/</code>. For example, you&rsquo;ll see that <code>promtail-config.yml</code> has a commented <code>docker</code> job for collecting all container logs in Loki which is quite neat.</p>
<p>With that ready, you can start it with:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up -d --force-recreate
</span></span></code></pre></div><h4 id="grafana-first-time-login">Grafana first time login</h4>
<p>Open Grafana GUI by navigating to <code>http://localhost:3008</code> and, since it&rsquo;s the first time, you can login using <code>admin</code> / <code>admin</code> and later change to proper credentials.</p>
<p>Go to Add your first data source and add Loki from the list.
Use these <code>http://loki:3100</code> for the <code>URL</code> field (or the port number value you had in your <code>.env</code> for it).
Press the <code>Save &amp; test</code> button.</p>
<p>Back to Grafana&rsquo;s home, add another data source and this time add InfluxDB from the list.</p>
<p>Use these values:</p>
<p>Query language: <code>Flux</code>.</p>
<p>URL: <code>http://influxdb:8086</code>.</p>
<p>Basic auth: <code>off</code>.</p>
<p>Organization: use what you have set for <code>DOCKER_INFLUXDB_INIT_ORG</code> in your <code>.env</code> file.
Press the <code>Save &amp; test</code> button.</p>
<h4 id="kuma">Kuma</h4>
<p>If you navigate to <code>http://localhost:3009</code> you&rsquo;ll have Kuma ready to add new monitors that will be very easy to setup to your health-check endpoints.</p>
<p><img loading="lazy" src="https://blog.sebastiansastre.co/img/kuma1.png"></p>
<h4 id="nginx">NGINX</h4>
<p>For deploying all this in your servers, all the services can be proxied in a simple way as you would expect, except Grafana and Kuma that will need a bit of attention for its WebSockets needs. Here is how you <a href="https://grafana.com/tutorials/run-grafana-behind-a-proxy/#configure-nginx">Configure NGINX for Grafana</a> and here how to have <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy">Kuma as Reverse Proxy</a>.</p>
<p>In conclusion, setting up an effective monitoring system for your infrastructure is an essential task that can help you identify potential issues before they become critical, detect anomalies, and keep an eye on system performance. While there are many tools and technologies available, Promtail, Telegraf, InfluxDB, Loki, Grafana, and Kuma are an excellent combination to get started with. And with the <a href="https://github.com/sebastianconcept/tlig:v1.0.2">TLIG v1.0.2</a> docker-compose file, hopefully you have some help to start doing it.</p>
<p>Remember that monitoring is an ongoing process that requires continuous improvement and adjustment. As your infrastructure grows, your monitoring system should also evolve to meet new challenges and requirements. With the right tools and strategies, you can ensure the health and reliability of your systems, and proactively address any issues that may arise.</p>
<p>I can easily see how this article might need a follow up to monitor a service managed with Kubernetes and running microservices developed with <a href="https://pharo.org/">Pharo</a> Smalltalk, of course 😊</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/grafana1.png" alt=""" loading="lazy" /></figure><p>Monitoring is an essential part of any modern infrastructure. It helps you keep an eye on system performance, detect anomalies, and identify potential issues before they become critical. With the right tools, monitoring can be a straightforward process that provides valuable insights into your system&rsquo;s health.</p>
<p>However, if you&rsquo;re like me, you can feel that setting up an effective monitoring system today can be daunting, especially for those who are new to the field. There are a plethora of tools and technologies available, and it can be difficult to know where to start.</p>
<p>In this article, I will show you how to set up Promtail, Telegraf, InfluxDB, Loki, and Grafana to monitor your system&rsquo;s performance and logs plus Kuma for service availability and alerts. I&rsquo;ve selected this toolchain because is reasonably straightforward to setup, shows good potential and flexibility for further growing needs.</p>
<p>And I&rsquo;ve created this <code>docker-compose</code> setup <a href="https://github.com/sebastianconcept/tlig:v1.0.2">TLIG v1.0.2</a> so you can have a reference to use as a starter.</p>
<p>By the end of this article, you will have a good understanding of how to set up a monitoring system for your infrastructure, and an idea on how you&rsquo;ll be able to customize it to meet some of your specific needs.</p>
<h4 id="what-to-monitor">What to Monitor</h4>
<p>With a system&rsquo;s design approach, we can identify the critical components of your infrastructure and then set up monitoring for those components so you have the right monitoring at the right levels. Any component in your service tech stack that is sensible to computing resources should be monitored for performance.</p>
<p>Sometimes a component could go down and the service would still be able to operate in a degraded state. Sometimes it won&rsquo;t be able to and you need to receive an adequate alarm pointing to that.</p>
<p>Let&rsquo;s review their categories:</p>
<p><strong>Infrastructure</strong>. Components can include servers, network devices, and storage systems. It&rsquo;s essential to monitor the health and performance of these components to detect issues that may lead to downtime or data loss. Kuma is going to help with this.</p>
<p><strong>Application Services</strong>. These are the backbone of any system. Monitoring the performance of the backend service is essential to ensure that requests are being processed efficiently and without errors. Telegraf will help here by efficiently collecting periodic measures as instructed in its customizable configuration and plugins.  The availability status is going to be covered by Kuma and its performance will be displayed in Grafana dashboards.</p>
<p><strong>Application Logs</strong>. Logs are the most obvious and expected way for headless services to leave a human readable trace of their activity for any system. They provide valuable information about what&rsquo;s happening in your application and have big value to be used to diagnose issues, identify trends, and even track user behavior. Loki will be used to have this accessible in one place and easy to query.</p>
<p><strong>Metrics</strong>. Metrics provide a high-level view of system performance over time. They help to identify trends and potential issues before they become critical. Metrics can include CPU usage, memory usage, disk space, network traffic and the usage of many other resources. Again, all of the performance details are going to be displayed in Grafana dashboards.</p>
<p>Let&rsquo;s take a moment to review each component and its function:</p>
<h4 id="promtail">Promtail</h4>
<p><a href="https://grafana.com/docs/loki/latest/clients/promtail/">Promtail</a> is a log collector and parser that sends logs to Loki. It can tail log files, filter logs based on patterns, and enrich log data with metadata. Promtail is designed to be highly scalable and can handle a large number of log streams.</p>
<h4 id="telegraf">Telegraf</h4>
<p><a href="https://github.com/influxdata/telegraf">Telegraf</a> is a plugin-driven server agent that can collect and send metrics to a variety of datastores. It supports a wide range of input plugins, including system metrics, network metrics, and Docker stats. Telegraf can also transform and aggregate metrics before sending them to the datastore.</p>
<h4 id="influxdb">InfluxDB</h4>
<p><a href="https://www.influxdata.com/">InfluxDB</a> is a time-series database that stores and queries time-stamped data. It provides a SQL-like query language for querying and aggregating data. InfluxDB is highly scalable and can handle millions of writes per second.</p>
<h4 id="loki">Loki</h4>
<p><a href="https://github.com/grafana/loki">Loki</a> is a horizontally scalable, highly available log aggregation system. It is designed to be a cost-effective way to handle large amounts of log data. Loki can ingest logs from various sources, including Promtail, and allows you to search and filter logs based on labels.</p>
<h4 id="grafana">Grafana</h4>
<p><a href="https://grafana.com/">Grafana</a> is a popular open-source platform for data visualization and monitoring. It can connect to various data sources, including InfluxDB and Loki, and provides an appealing web-based interface for creating and sharing dashboards. Grafana supports a wide range of visualization options, including charts, tables, and alerts.</p>
<p>Now that we have a good understanding of each component&rsquo;s role in the monitoring stack, let&rsquo;s move on to setting up the monitoring system using Docker Compose as found in  <a href="https://github.com/sebastianconcept/tlig:v1.0.2">TLIG v1.0.2</a>.</p>
<p><code>docker-compose.yml</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tlig</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">loki</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">grafana/loki:2.4.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./conf/loki:/etc/loki:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${DOCKER_LOKI_PORT}:3100      </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span>-<span class="l">config.file=/etc/loki/loki-config.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">promtail</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tlig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">promtail</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">grafana/promtail:2.4.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/log:/var/log:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./conf/promtail:/etc/promtail:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span>-<span class="l">config.file=/etc/promtail/promtail-config.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tlig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">influxdb</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">influxdb:2.1.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data/influxdb:/var/lib/influxdb2:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entrypoint</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;./entrypoint.sh&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="kc">on</span>-<span class="l">failure:10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${DOCKER_INFLUXDB_INIT_PORT}:8086</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tlig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">telegraf</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">telegraf:1.19</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${TELEGRAF_CFG_PATH}:/etc/telegraf/telegraf.conf:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">influxdb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tlig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">grafana</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">grafana/grafana-oss:8.4.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="l">${USER_ID}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data/grafana:/var/lib/grafana:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">telegraf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">loki</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${GRAFANA_PORT}:3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tlig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kuma</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">louislam/uptime-kuma:1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">init</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">kuma</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data/kuma:/app/data:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${KUMA_PORT}:3001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">security_opt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="kc">no</span>-<span class="l">new-privileges:true</span><span class="w">
</span></span></span></code></pre></div><p>Create a <code>.env</code> for yourself based in this <code>.env.sample</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_MODE</span><span class="o">=</span>setup
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## Environment variables used during the setup and operation of the stack</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Primary InfluxDB admin/superuser credentials</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_USERNAME</span><span class="o">=</span>admin
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Make sure this pwd is not too short so Influx doesn&#39;t reject it.</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_PASSWORD</span><span class="o">=</span>adminpwd1234
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate your own with `openssl rand -hex 32`</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_ADMIN_TOKEN</span><span class="o">=</span>blahblahblahrandomstringtoken
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Primary InfluxDB organization &amp; bucket definitions</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_ORG</span><span class="o">=</span>yourorganization
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_BUCKET</span><span class="o">=</span>telegraf 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Primary InfluxDB bucket retention period</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="c1"># NOTE: Valid units are nanoseconds (ns), microseconds(us), milliseconds (ms)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># seconds (s), minutes (m), hours (h), days (d), and weeks (w).</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_RETENTION</span><span class="o">=</span>4d
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># InfluxDB port &amp; hostname definitions</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_PORT</span><span class="o">=</span><span class="m">8086</span> 
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_INFLUXDB_INIT_HOST</span><span class="o">=</span>influxdb 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Telegraf configuration file</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1"># Will be mounted to container and used as telegraf configuration</span>
</span></span><span class="line"><span class="cl"><span class="nv">TELEGRAF_CFG_PATH</span><span class="o">=</span>./conf/telegraf/telegraf.conf
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Loki port definition</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_LOKI_PORT</span><span class="o">=</span><span class="m">3100</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_LOKI_GRPC_PORT</span><span class="o">=</span><span class="m">9096</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Promtail port definition</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_PROMTAIL_PORT</span><span class="o">=</span><span class="m">9080</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_PROMTAIL_GRPC_PORT</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Grafana port definition</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Grafana default uses 3000 but let&#39;s move that elsewere, since that&#39;s</span>
</span></span><span class="line"><span class="cl"><span class="c1"># a popular port for many developing setups.</span>
</span></span><span class="line"><span class="cl"><span class="nv">GRAFANA_PORT</span><span class="o">=</span><span class="m">3008</span>
</span></span><span class="line"><span class="cl"><span class="nv">USER_ID</span><span class="o">=</span>xxxx <span class="c1">#replace xxxx with what you have for `$(id -u)`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Kuma port definition</span>
</span></span><span class="line"><span class="cl"><span class="nv">KUMA_PORT</span><span class="o">=</span><span class="m">3009</span>
</span></span></code></pre></div><p>For running it in one host, you should be good by setting the username, password, token, organization name and bucket name for InfluxDB and all the ports and <code>USER_ID</code>.</p>
<p>You&rsquo;ll be able to further config Promtail, Loki and Telegraf editing the files in <code>./conf/</code>. For example, you&rsquo;ll see that <code>promtail-config.yml</code> has a commented <code>docker</code> job for collecting all container logs in Loki which is quite neat.</p>
<p>With that ready, you can start it with:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up -d --force-recreate
</span></span></code></pre></div><h4 id="grafana-first-time-login">Grafana first time login</h4>
<p>Open Grafana GUI by navigating to <code>http://localhost:3008</code> and, since it&rsquo;s the first time, you can login using <code>admin</code> / <code>admin</code> and later change to proper credentials.</p>
<p>Go to Add your first data source and add Loki from the list.
Use these <code>http://loki:3100</code> for the <code>URL</code> field (or the port number value you had in your <code>.env</code> for it).
Press the <code>Save &amp; test</code> button.</p>
<p>Back to Grafana&rsquo;s home, add another data source and this time add InfluxDB from the list.</p>
<p>Use these values:</p>
<p>Query language: <code>Flux</code>.</p>
<p>URL: <code>http://influxdb:8086</code>.</p>
<p>Basic auth: <code>off</code>.</p>
<p>Organization: use what you have set for <code>DOCKER_INFLUXDB_INIT_ORG</code> in your <code>.env</code> file.
Press the <code>Save &amp; test</code> button.</p>
<h4 id="kuma">Kuma</h4>
<p>If you navigate to <code>http://localhost:3009</code> you&rsquo;ll have Kuma ready to add new monitors that will be very easy to setup to your health-check endpoints.</p>
<p><img loading="lazy" src="https://blog.sebastiansastre.co/img/kuma1.png"></p>
<h4 id="nginx">NGINX</h4>
<p>For deploying all this in your servers, all the services can be proxied in a simple way as you would expect, except Grafana and Kuma that will need a bit of attention for its WebSockets needs. Here is how you <a href="https://grafana.com/tutorials/run-grafana-behind-a-proxy/#configure-nginx">Configure NGINX for Grafana</a> and here how to have <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy">Kuma as Reverse Proxy</a>.</p>
<p>In conclusion, setting up an effective monitoring system for your infrastructure is an essential task that can help you identify potential issues before they become critical, detect anomalies, and keep an eye on system performance. While there are many tools and technologies available, Promtail, Telegraf, InfluxDB, Loki, Grafana, and Kuma are an excellent combination to get started with. And with the <a href="https://github.com/sebastianconcept/tlig:v1.0.2">TLIG v1.0.2</a> docker-compose file, hopefully you have some help to start doing it.</p>
<p>Remember that monitoring is an ongoing process that requires continuous improvement and adjustment. As your infrastructure grows, your monitoring system should also evolve to meet new challenges and requirements. With the right tools and strategies, you can ensure the health and reliability of your systems, and proactively address any issues that may arise.</p>
<p>I can easily see how this article might need a follow up to monitor a service managed with Kubernetes and running microservices developed with <a href="https://pharo.org/">Pharo</a> Smalltalk, of course 😊</p>
]]></content:encoded>
    </item>
    <item>
      <title>Bringing TensorFlow to Pharo</title>
      <link>https://blog.sebastiansastre.co/posts/bringing-tensorflow-to-pharo/</link>
      <pubDate>Thu, 16 Feb 2023 20:54:44 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/bringing-tensorflow-to-pharo/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/TensorFlowPlugin.jpg" type="image/jpeg" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/TensorFlowPlugin.jpg" alt=""" loading="lazy" /></figure><p>Pharo is a powerful open-source Smalltalk-based programming language and environment that has been widely used for research and development in various fields. Specially Pharo at <a href="https://inria.fr">Inria</a>, France&rsquo;s National Institute for Research in Digital Science and Technology. However, the lack of a native plugin for <a href="https://www.tensorflow.org/">TensorFlow</a>, a popular open-source machine learning framework, has been a major limitation for high efficiency AI development with Pharo. I&rsquo;ve written this post to let you know that, I&rsquo;ve proposed a Google Summer of Code 2023 project to create a <a href="https://gsoc.pharo.org/tensor-flow-plugin">TensorFlow plugin for Pharo</a>, which will unlock the full potential of Pharo for production grade AI development.</p>
<p>The goal is to create a TensorFlow plugin for <a href="https://pharo.org/">Pharo</a> that can be used directly by Pharo&rsquo;s C VM, allowing Pharo to take full advantage of the silicon of your graphical processing unit so you have an efficient and powerful way to do mining of that GPU power for any algebra intensive peoject, Machine Learning and AI projects in particular. With this plugin, Pharo will be able to perform AI tasks such as deep learning, neural network training, and image recognition using TensorFlow with no need for additional bridges or third-party libraries that could add significant complexity to any project setup and development and debug process.</p>
<p>The completed project has the ambition to deliver a plugin that provides all the primitives to the TensorFlow API, with at least one unit test to ensure quality assurance. This will enable Pharo developers to use TensorFlow for world class AI development, making Pharo a top-tier platform for machine learning or any synthetic intelligence application.</p>
<p>The project requires basic C programming skills, familiarity with how the Pharo VM is created, basic familiarity with Slang, and strong communication skills. Prior experience in creating a Pharo plugin is preferred.</p>
<p><a href="https://rmod-files.lille.inria.fr/FreeBooks/CollectiveNBlueBook/greenberg.pdf">Extending the Squeak Virtual Machine</a> by Andrew C. Greenberg is a great read for getting up to speed on the basics of extending Smalltalk VM functionality.</p>
<p>Together with Sebastian Jordan and Oleksandr Zaitsev from the Pharo community, we&rsquo;ll mentoring the students taking on this project to help them make it real. As a first step in this direction, I&rsquo;ve published <a href="https://blog.sebastiansastre.co/article/how-to-create-a-pharo-smalltalk-plugin">How to Create a Pharo Smalltalk Plugin</a> here to remove any friction of the developing process of Pharo plugins.</p>
<p>Hoping Google likes it and becomes real!</p>
<p>Want to be part of this?</p>
<p>Check the eligibility criteria in <a href="https://gsoc.pharo.org/">Google Summer of Code 2023: Call for Students</a> and help us make this a new impactful resource for a new reality.</p>
<p>Creating a TensorFlow plugin for Pharo is an important step towards unlocking the full potential of this powerful language for AI development. I believe that this Google Summer of Code 2023 project will be an exciting opportunity for any student who is passionate about machine learning and interested in contributing to an open-source community. With the right skills and guidance, we can make TensorFlow a first-class citizen in the Pharo ecosystem, and enable Pharo developers to take on AI challenges with confidence.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/TensorFlowPlugin.jpg" alt=""" loading="lazy" /></figure><p>Pharo is a powerful open-source Smalltalk-based programming language and environment that has been widely used for research and development in various fields. Specially Pharo at <a href="https://inria.fr">Inria</a>, France&rsquo;s National Institute for Research in Digital Science and Technology. However, the lack of a native plugin for <a href="https://www.tensorflow.org/">TensorFlow</a>, a popular open-source machine learning framework, has been a major limitation for high efficiency AI development with Pharo. I&rsquo;ve written this post to let you know that, I&rsquo;ve proposed a Google Summer of Code 2023 project to create a <a href="https://gsoc.pharo.org/tensor-flow-plugin">TensorFlow plugin for Pharo</a>, which will unlock the full potential of Pharo for production grade AI development.</p>
<p>The goal is to create a TensorFlow plugin for <a href="https://pharo.org/">Pharo</a> that can be used directly by Pharo&rsquo;s C VM, allowing Pharo to take full advantage of the silicon of your graphical processing unit so you have an efficient and powerful way to do mining of that GPU power for any algebra intensive peoject, Machine Learning and AI projects in particular. With this plugin, Pharo will be able to perform AI tasks such as deep learning, neural network training, and image recognition using TensorFlow with no need for additional bridges or third-party libraries that could add significant complexity to any project setup and development and debug process.</p>
<p>The completed project has the ambition to deliver a plugin that provides all the primitives to the TensorFlow API, with at least one unit test to ensure quality assurance. This will enable Pharo developers to use TensorFlow for world class AI development, making Pharo a top-tier platform for machine learning or any synthetic intelligence application.</p>
<p>The project requires basic C programming skills, familiarity with how the Pharo VM is created, basic familiarity with Slang, and strong communication skills. Prior experience in creating a Pharo plugin is preferred.</p>
<p><a href="https://rmod-files.lille.inria.fr/FreeBooks/CollectiveNBlueBook/greenberg.pdf">Extending the Squeak Virtual Machine</a> by Andrew C. Greenberg is a great read for getting up to speed on the basics of extending Smalltalk VM functionality.</p>
<p>Together with Sebastian Jordan and Oleksandr Zaitsev from the Pharo community, we&rsquo;ll mentoring the students taking on this project to help them make it real. As a first step in this direction, I&rsquo;ve published <a href="https://blog.sebastiansastre.co/article/how-to-create-a-pharo-smalltalk-plugin">How to Create a Pharo Smalltalk Plugin</a> here to remove any friction of the developing process of Pharo plugins.</p>
<p>Hoping Google likes it and becomes real!</p>
<p>Want to be part of this?</p>
<p>Check the eligibility criteria in <a href="https://gsoc.pharo.org/">Google Summer of Code 2023: Call for Students</a> and help us make this a new impactful resource for a new reality.</p>
<p>Creating a TensorFlow plugin for Pharo is an important step towards unlocking the full potential of this powerful language for AI development. I believe that this Google Summer of Code 2023 project will be an exciting opportunity for any student who is passionate about machine learning and interested in contributing to an open-source community. With the right skills and guidance, we can make TensorFlow a first-class citizen in the Pharo ecosystem, and enable Pharo developers to take on AI challenges with confidence.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Create a Pharo Smalltalk Plugin</title>
      <link>https://blog.sebastiansastre.co/posts/how-to-create-a-pharo-smalltalk-plugin/</link>
      <pubDate>Fri, 10 Feb 2023 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/how-to-create-a-pharo-smalltalk-plugin/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/addPharoVMRepo.gif" type="image/gif" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/addPharoVMRepo.gif" alt=""" loading="lazy" /></figure><p><strong>Introduction</strong></p>
<p>When working with Smalltalk, you sometimes wish to access functionality that exists only in a specific library or technology that you cannot or simply don&rsquo;t want to reinvent. When you are in this scenario, two options come to mind:</p>
<ol>
<li>FFI, or Foreign Function Interface, which is a mechanism to enable a program written in one programming language to interact with code written in another programming language. Essentially, a &ldquo;bridge&rdquo; that allows them to communicate and exchange information with each other.</li>
<li>Somehow make the Smalltalk Virtual Machine to have more primitives that extend its default functionality to produce this communication back and forth, from the Smalltalk code and these other libraries.</li>
</ol>
<p>In developing terms, FFI is usually the fastest way to achieve this and, for Pharo, is extensibly covered in <a href="http://books.pharo.org/booklet-uffi/pdf/2020-02-12-uFFI-V1.0.1">Unified FFI - Calling Foreign Functions from Pharo</a> by Guillermo Polito, Stéphane Ducasse, Pablo Tesone and Ted Brunzie.</p>
<p>In this article, we&rsquo;ll delve into the less-traveled road of extending the Pharo Virtual Machine with an external plugin by creating a HelloWorldPlugin. With this, I want to provide some clarity that could demystify the process of making extensions in this way by understanding the steps currently needed to achieve this in Pharo 9, as per February 2023.</p>
<p><strong>Motivation</strong></p>
<p>The primary motivation behind extending the Pharo Virtual Machine with a plugin is often performance, but in some cases, it can also simplify the process of accessing specific functionality. For instance, if you have a favorite library that can only be used by Python or Lua, as a Smalltalker, you may be forced to create a bridge to these technologies, adding additional setup instructions and increasing the complexity of your application&rsquo;s technology stack. With the use of plugins, however, you can extend Smalltalk and keep the complexity of your tech stack to a minimum. After all, Smalltalk when used keeping an elegant software design, can be considered a tool for dominating complexity.</p>
<p>Back to performance, you will easily find cases where the FFI marshaling back and forth process might be considered too costly, and your Smalltalk application would find a more desirable alternative in getting dedicated primitives from a plugin to use the targeted functionality. You may be using plugins like FilePlugin, FloatArrayPlugin, SocketPlugin, and UnixOSProcessPlugin without even realizing it and, as you can imagine, they are all performance critical.</p>
<p>Let&rsquo;s work on your new <code>HelloWorldPlugin</code> now. Here is the outline of the development cycle for it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">┌───────────┐                                
</span></span><span class="line"><span class="cl">│MyPlugins/ │                                
</span></span><span class="line"><span class="cl">└──┬┬───────┘               Plugin image     
</span></span><span class="line"><span class="cl">   ││  ┌──────────────────┐       │          
</span></span><span class="line"><span class="cl">   └┼─▶│HelloWorldPlugin/ │◀──────┘          
</span></span><span class="line"><span class="cl">    │  └──────────────────┘                  
</span></span><span class="line"><span class="cl">    │  ┌──────────┐                          
</span></span><span class="line"><span class="cl">    └─▶│pharo-vm/ │◀──────────────┐          
</span></span><span class="line"><span class="cl">       └──────────┘               │          
</span></span><span class="line"><span class="cl">                       Code to produce builds
</span></span></code></pre></div><p><strong>Setup procedure</strong></p>
<p>You&rsquo;ll need to do this once:</p>
<ol>
<li>A directory where you&rsquo;ll use a Pharo image for coding your plugin. Let&rsquo;s refer to this as &ldquo;the plugin image&rdquo;.</li>
<li>A directory with a clone of the <a href="https://github.com:pharo-project/pharo-vm.git">pharo-vm project</a></li>
<li>In the plugin image, you&rsquo;ll install <code>BaselineOfVMMaker</code> which has all the infrastructure needed to produce the sources that you will later compile in a build process.</li>
<li>In the plugin image, you&rsquo;ll checkout the <code>Pharo9</code> branch from origin for compatibility with this guide.</li>
</ol>
<p><strong>Development procedure</strong></p>
<p>You&rsquo;ll iterate this part producing builds of your plugin until you get to the final version:</p>
<ol>
<li>In the plugin image, you&rsquo;ll locally create a new branch using that <code>Pharo9</code> branch. That is your plugin development starting point. For this exercise we&rsquo;ll create a this branch naming it <code>HelloWorldPlugin</code>.</li>
<li>In the plugin image, you&rsquo;ll add the code of the <code>HelloWorldPlugin</code> class and its methods.</li>
<li>In the plugin image, you&rsquo;ll update (needed only once) which the external plugins are going to get generated adding <code>HelloWorldPlugin</code> to the list and you&rsquo;ll commit that. Note: you will only pick to commit that change and not any other automated change that Iceberg might detect.</li>
</ol>
<p><strong>Building procedure</strong></p>
<ol>
<li>From the <code>MyPlugin</code> directory, you&rsquo;ll run the <code>cmake</code> command to configure the environment for compilation.</li>
<li>From the <code>MyPlugin/build</code> directory, you&rsquo;ll run the <code>make</code>  command to compile and produce the build.</li>
<li>From the <code>MyPlugin/pharo-vm</code> directory, you&rsquo;ll edit <code>plugins.cmake</code> to make it include in the build the generated sources of <code>HelloWorldPlugin</code>.</li>
<li>From the <code>MyPlugin</code> directory, you&rsquo;ll run the <code>cmake</code> command again to configure everything including the generated code of your plugin.</li>
<li>From the <code>MyPlugin</code> directory, you&rsquo;ll run the <code>make</code> command to compile producing the binaries.</li>
</ol>
<p><strong>Note</strong>: If you don&rsquo;t follow the sequence of the development cycle, you&rsquo;ll notice that <code>cmake</code> can easily run into problems by not finding the sources of <code>HelloWorldPlugin</code> automatically generated in <code>build/generated/64/plugins/src/HelloWorldPlugin</code>. The first round of <code>cmake</code> and <code>make</code> (steps 8 and 9) are not actually to produce the binaries you&rsquo;ll want, but only to have <code>make</code> producing the generated sources that you&rsquo;ll use <em>after</em> <code>plugins.cmake</code> gets edited to include your plugin and then built with step&rsquo;s 5 <code>make</code>.</p>
<p><strong>Setup</strong></p>
<p>Go to your favorite folder for your code and prepare a directory to work with this, here I&rsquo;ll call it <code>MyPlugins</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="nb">cd</span> ~/yourFavoriteCodeDirForThis
</span></span><span class="line"><span class="cl">$ mkdir MyPlugins
</span></span><span class="line"><span class="cl">$ <span class="nb">cd</span> MyPlugins
</span></span></code></pre></div><p>Clone the Pharo code for building Pharo VMs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git clone git@github.com:pharo-project/pharo-vm.git
</span></span></code></pre></div><p>Create the <code>HelloWorldPlugin</code> directory to have the image to produce the new code, so go ahead and grab a fresh pharo 9 and start it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ mkdir HelloWorldPlugin$ <span class="nb">pwd</span>
</span></span><span class="line"><span class="cl">...blah/MyPlugins/HelloWorldPlugin
</span></span><span class="line"><span class="cl">$  curl get.pharo.org/64/90 <span class="p">|</span> bash
</span></span><span class="line"><span class="cl">$  curl get.pharo.org/64/vm90 <span class="p">|</span> bash
</span></span><span class="line"><span class="cl">$ ./pharo
</span></span></code></pre></div><p>Off-topic, if you are like me, the first thing to do on fresh Pharo images is to tune the visuals by loading in a Playground:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Metacello</span> <span class="nb">new</span> 
</span></span><span class="line"><span class="cl">    <span class="nf">baseline:</span> <span class="s">&#39;PharoDawnTheme&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">repository:</span> <span class="s">&#39;github://sebastianconcept/PharoDawnTheme&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">load</span><span class="p">.</span>
</span></span></code></pre></div><p>Now, from this plugin image, add the repository from the cloned pharo-vm project using Iceberg and checkout the <code>Pharo9</code> branch:</p>
<p>Now, load <code>BaselineOfVMMaker</code>:
<img alt="Loading BaselineOfVMMaker" loading="lazy" src="https://blog.sebastiansastre.co/img/loadVMMakerBaseline.gif"></p>
<p>Next, from the <code>Pharo9</code> branch, create the new <code>HelloWorldPlugin</code> branch:
<img alt="Branch HelloWorldPlugin out of Pharo9" loading="lazy" src="https://blog.sebastiansastre.co/img/branchHelloWorldPlugin.gif"></p>
<p>And now we can finally add your plugin code.</p>
<p>Go to <code>SmartSyntaxInterpreterPlugin</code> class. This one will be the superclass of your plugin.</p>
<p><strong>Important</strong>: maintain <code>HelloWorldPlugin</code> in the <code>VMMaker-Plugins</code> package as required for automated code generation.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">SmartSyntaxInterpreterPlugin</span> <span class="nf">subclass:</span> <span class="ss">#HelloWorldPlugin</span>
</span></span><span class="line"><span class="cl">	<span class="nf">instanceVariableNames:</span> <span class="s">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">classVariableNames:</span> <span class="s">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">package:</span> <span class="s">&#39;VMMaker-Plugins&#39;</span>
</span></span></code></pre></div><p>In the <strong>class side</strong> of <code>HelloWorldPlugin</code> add the <code>moduleNameAndVersion</code> method:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">moduleNameAndVersion</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="o">^</span> <span class="bp">self</span> <span class="nf">moduleName</span> <span class="nf">,</span><span class="nc">Character</span> <span class="nf">space</span> <span class="nf">asString</span> <span class="nf">,</span> <span class="nc">Date</span> <span class="nf">today</span> <span class="nf">asString</span>
</span></span></code></pre></div><p>In the <strong>instance side</strong> of <code>HelloWorldPlugin</code> add the <code>primitiveGetHelloString</code> and <code>stringFromCString:</code> methods:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">primitiveGetHelloString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	&lt;<span class="k">export:</span> true&gt;
</span></span><span class="line"><span class="cl">	<span class="nv">interpreterProxy</span>
</span></span><span class="line"><span class="cl">		<span class="nf">pop:</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">		<span class="nf">thenPush:</span> (<span class="nv">interpreterProxy</span> <span class="nf">stringFromCString:</span>
</span></span><span class="line"><span class="cl">			 <span class="s">&#39;Hello from your HelloWorldPlugin.&#39;</span>)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">stringFromCString:</span> <span class="nv">aCString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c">&#34;Answer a new String copied from a null-terminated C string.
</span></span></span><span class="line"><span class="cl"><span class="c">    Caution: This may invoke the garbage collector.&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	&lt;<span class="k">var:</span> &#39;aCString&#39; type: #&#39;const char *&#39;&gt;
</span></span><span class="line"><span class="cl">	<span class="o">|</span><span class="nv"> len newString </span><span class="o">|</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="nv">len</span> <span class="o">:=</span> <span class="bp">self</span> <span class="nf">strlen:</span> <span class="nv">aCString</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">	<span class="nv">newString</span> <span class="o">:=</span> <span class="nv">interpreterProxy</span>
</span></span><span class="line"><span class="cl">		             <span class="nf">instantiateClass:</span> <span class="nv">interpreterProxy</span> <span class="nf">classString</span>
</span></span><span class="line"><span class="cl">		             <span class="nf">indexableSize:</span> <span class="nv">len</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">	<span class="nv">newString</span> <span class="nf">ifNil:</span> [ 
</span></span><span class="line"><span class="cl">		<span class="o">^</span> <span class="nv">interpreterProxy</span> <span class="nf">primitiveFailFor:</span> <span class="nc">PrimErrNoMemory</span> ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">	<span class="bp">self</span>
</span></span><span class="line"><span class="cl">		<span class="nf">strncpy:</span> (<span class="nv">interpreterProxy</span> <span class="nf">arrayValueOf:</span> <span class="nv">newString</span>)
</span></span><span class="line"><span class="cl">		<span class="o">_</span><span class="err">:</span> <span class="nv">aCString</span>
</span></span><span class="line"><span class="cl">		<span class="o">_</span><span class="err">:</span> <span class="nv">len</span><span class="p">.</span> <span class="c">&#34;(char *)strncpy()&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="o">^</span> <span class="nv">newString</span>
</span></span></code></pre></div><p>If you&rsquo;re wondering about that unusual looking code, that&rsquo;s <a href="http://wiki.squeak.org/squeak/slang">Slang</a>, I&rsquo;m not going to cover that here but is suffice for now to know that it&rsquo;s a code generation tool originally used in Squeak and now used to produce the Cog virtual machine used in Pharo. Slang converts Smalltalk code into C source code, which is then compiled to produce a virtual machine or, as you&rsquo;ll soon see, your <code>HelloWorldPlugin</code>. Slang basically helps to simplify the process of extending the Smalltalk virtual machine by providing an interface for generating multiplatform C code directly from Smalltalk code. This makes Smalltalk more maintainable and portable.</p>
<p>Done and said that, you&rsquo;re almost ready to commit. The only missing piece of development is to tell <code>VMMaker</code> to use your class to create an external plugin.</p>
<p>Browse the <code>PharoVMMaker&gt;&gt;generate:memoryManager:</code> method and add the name <code>HelloWorldPlugin</code> to the array of <code>external</code> plugins.</p>
<p>The full method should be:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">generate:</span> <span class="nv">interpreterClass</span> <span class="nf">memoryManager:</span> <span class="nv">memoryManager</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="o">|</span><span class="nv"> platformDirectory </span><span class="o">|</span>
</span></span><span class="line"><span class="cl">	<span class="nc">Author</span> <span class="nf">useAuthor:</span> <span class="s">&#39;vmMaker&#39;</span> <span class="nf">during:</span> [ 
</span></span><span class="line"><span class="cl">		<span class="nc">VMMakerConfiguration</span> <span class="nf">initializeForPharo</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">		(<span class="nv">interpreterClass</span> <span class="nf">bindingOf:</span> <span class="ss">#COGMTVM</span>) <span class="nf">value:</span> <span class="bp">false</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">		<span class="nv">platformDirectory</span> <span class="o">:=</span> <span class="bp">self</span> <span class="nf">platformDirectoryFor:</span> <span class="nv">memoryManager</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">		[ 
</span></span><span class="line"><span class="cl">		(<span class="nc">VMMaker</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">makerFor:</span> <span class="nv">interpreterClass</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">and:</span> <span class="nc">StackToRegisterMappingCogit</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">with:</span> { 
</span></span><span class="line"><span class="cl">					 <span class="ss">#COGMTVM</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="bp">false</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="ss">#ObjectMemory</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="nv">memoryManager</span> <span class="nf">name</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="ss">#MULTIPLEBYTECODESETS</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="bp">true</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="ss">#bytecodeTableInitializer</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="ss">#initializeBytecodeTableForSqueakV3PlusClosuresSistaV1Hybrid</span> }
</span></span><span class="line"><span class="cl">			 <span class="nf">to:</span> <span class="nv">platformDirectory</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">platformDir:</span> <span class="nv">platformDirectory</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">including:</span> <span class="ss">#(</span>  <span class="ss">)</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">configuration:</span> <span class="nc">VMMakerConfiguration</span>)
</span></span><span class="line"><span class="cl">			<span class="nf">stopOnErrors:</span> <span class="nv">stopOnErrors</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="nf">internal:</span> <span class="ss">#(</span>  <span class="ss">)</span>
</span></span><span class="line"><span class="cl">			<span class="nf">external:</span>
</span></span><span class="line"><span class="cl">				<span class="ss">#(</span> <span class="ss">FilePlugin</span> <span class="ss">SurfacePlugin</span> <span class="ss">FloatArrayPlugin</span> <span class="ss">HelloWorldPlugin</span> <span class="ss">)</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="nf">generateInterpreterFile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="nf">generateCogitFiles</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="nf">generateExternalPlugins</span> ] <span class="nf">valueSupplyingAnswer:</span> <span class="bp">true</span> ]
</span></span></code></pre></div><p>Now you can commit your changes completing the <em>development procedure</em> and ready to move on to the <em>building procedure</em>.</p>
<p><img alt="Commit the HelloWorldPlugin code in the HelloWorldPlugin branch" loading="lazy" src="https://blog.sebastiansastre.co/img/codeHelloWorldPlugin.gif"></p>
<p>For the building procedure and to evade a chicken-egg kind of problem, we&rsquo;re going to do a first round of running <code>cmake</code> and <code>make</code> commands that will autogenerate the C source code based on your methods written in slang and leave these source files ready to use at <code>build/generated/64/plugins/src/HelloWorldPlugin</code> (and likely also <code>build/generated/32/...</code>).</p>
<p>Go to your <code>MyPlugins/</code> directory and run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cmake -S pharo-vm -B build
</span></span></code></pre></div><p>After this run, you should see a list of plugins <em>without</em> <code>HelloWorldPlugin</code>, this is expected at this time. Also expected, is that you will not have yet the <code>build/generated/</code> directory. We are going to produce it in the next command.</p>
<p>From <code>MyPlugins/build</code> run the <code>make</code> command to generate and compile everything for the first time:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ make install
</span></span></code></pre></div><p>At the end of this process, you should see that <code>build/generated/64/plugins/src/HelloWorldPlugin</code> does exists now.</p>
<p>Time to tell cmake that it can configure a build including your generated sources. With your favorite editor open for editing the <code>MyPlugins/pharo-vm/plugins.cmake</code> file and add this above or below the configuration for <code>Surface Plugin</code>. We&rsquo;re basically going to tell <code>cmake</code> that it should do with <code>HelloWorldPlugin</code> the same as with <code>SurfacePlugin</code>, build it from the sources as an external plugin.</p>
<p>Your edited <code>plugins.cmake</code> now should look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl"><span class="err">...</span> <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="c"># HelloWorld Plugin
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">add_vm_plugin</span><span class="p">(</span><span class="s">HelloWorldPlugin</span> 
</span></span><span class="line"><span class="cl">	<span class="o">${</span><span class="nv">PHARO_CURRENT_GENERATED</span><span class="o">}</span><span class="s">/plugins/src/HelloWorldPlugin/HelloWorldPlugin.c</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="c"># Surface Plugin
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">add_vm_plugin</span><span class="p">(</span><span class="s">SurfacePlugin</span> 
</span></span><span class="line"><span class="cl">	<span class="o">${</span><span class="nv">PHARO_CURRENT_GENERATED</span><span class="o">}</span><span class="s">/plugins/src/SurfacePlugin/SurfacePlugin.c</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="c"># FloatArray Plugin
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">add_vm_plugin</span><span class="p">(</span><span class="s">FloatArrayPlugin</span> 
</span></span><span class="line"><span class="cl">	<span class="o">${</span><span class="nv">PHARO_CURRENT_GENERATED</span><span class="o">}</span><span class="s">/plugins/src/FloatArrayPlugin/FloatArrayPlugin.c</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">...
</span></span></span></code></pre></div><p>Next, from <code>MyPlugins</code> directory, run <code>cmake</code> again:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cmake -S pharo-vm -B build
</span></span></code></pre></div><p>And at the end, you should find it lists your plugin:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">... 
</span></span><span class="line"><span class="cl">   B2DPlugin
</span></span><span class="line"><span class="cl">   BitBltPlugin
</span></span><span class="line"><span class="cl">   DSAPrims
</span></span><span class="line"><span class="cl">   FileAttributesPlugin
</span></span><span class="line"><span class="cl">   FilePlugin
</span></span><span class="line"><span class="cl">   FloatArrayPlugin
</span></span><span class="line"><span class="cl">   HelloWorldPlugin
</span></span><span class="line"><span class="cl">   JPEGReadWriter2Plugin
</span></span><span class="line"><span class="cl">	...
</span></span></code></pre></div><p>And finally, to build your plugin, from <code>MyPlugins/build</code> run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ make install
</span></span></code></pre></div><p>Once the compilation finishes, we can check the results.</p>
<p>As I&rsquo;m doing this on <code>macOS</code> the resulting directory for the binaries is <code>build/vm/Debug/Pharo.app/Contents/MacOS/Plugins</code> .</p>
<p>And there it is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ ls -la build/vm/Debug/Pharo.app/Contents/MacOS/Plugins 
</span></span><span class="line"><span class="cl">total <span class="m">83232</span>
</span></span><span class="line"><span class="cl">drwxr-xr-x  <span class="m">54</span> seb  staff     <span class="m">1728</span> Feb <span class="m">10</span> 19:01 .
</span></span><span class="line"><span class="cl">drwxr-xr-x   <span class="m">4</span> seb  staff      <span class="m">128</span> Feb <span class="m">10</span> 19:01 ..
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff   <span class="m">135352</span> Feb <span class="m">10</span> 19:01 libB2DPlugin.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">97864</span> Feb <span class="m">10</span> 19:01 libBitBltPlugin.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">35952</span> Feb <span class="m">10</span> 19:01 libDSAPrims.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">77256</span> Feb <span class="m">10</span> 19:01 libFileAttributesPlugin.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">88800</span> Feb <span class="m">10</span> 19:01 libFilePlugin.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">22352</span> Feb <span class="m">10</span> 19:01 libFloatArrayPlugin.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">17624</span> Feb <span class="m">10</span> 19:01 libHelloWorldPlugin.dylib
</span></span></code></pre></div><p>Let&rsquo;s see it in action:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ build/vm/Debug/Pharo.app/Contents/MacOS/Pharo build/vmmaker/image/Pharo10.0.1-0-64bit-0542643.image --interactive
</span></span></code></pre></div><p>Create an <code>Object</code> subclass:  <code>HelloWorld</code> class and add this instance method:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">getHelloString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	&lt;<span class="k">primitive:</span> &#39;primitiveGetHelloString&#39; module: &#39;HelloWorldPlugin&#39;&gt;
</span></span><span class="line"><span class="cl">	<span class="bp">self</span> <span class="nf">primitiveFailed</span>
</span></span></code></pre></div><p>And test it from this snippet:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">listLoadedModules</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">listBuiltinModules</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">hello</span> <span class="o">:=</span> <span class="nc">HelloWorld</span> <span class="nb">new</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">hello</span> <span class="nf">getHelloString</span><span class="p">.</span> 
</span></span></code></pre></div><p><img alt="Using the plugin primitive from Smalltalk" loading="lazy" src="https://blog.sebastiansastre.co/img/testingHelloWorld.gif"></p>
<p>With all going well, in your workspace you&rsquo;ll be getting the string produced by your new primitive.</p>
<p>Notice that is you check <code>listLoadedModules</code> before using <code>getHelloString</code> your plugin is not going to be loaded but if you check after using it for the first time, you&rsquo;ll find it there showing that Pharo lazy loads the plugins.</p>
<p><strong>Benchmarking</strong></p>
<p>As it gets revealed by now, creating a Pharo Smalltalk plugin requires significant effort, so to get a sense of proportions of what you get for it, lets compare how your plugin performs against a C shared library.</p>
<p>Rust is known to have a general computing performance almost at par with C so I&rsquo;ve built this simple hello world lib <a href="https://github.com/sebastianconcept/librusthelloworld">here</a> that can be used to measure this. The README.md file has instructions to build the library.</p>
<p>Go ahead in and build it for release.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git clone git@github.com:sebastianconcept/librusthelloworld.git
</span></span><span class="line"><span class="cl">$ cargo build --release
</span></span></code></pre></div><p>You will find the built library in:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ target/release/librusthelloworld.dylib
</span></span></code></pre></div><p>Create a symlink in the image dir so it can find <code>target/release/librusthelloworld.dylib</code>.</p>
<p>Create <code>RustHelloWorldLibrary</code> as subclass of <code>FFILibrary</code> and add this method in the <em>instance side</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">macModuleName</span>
</span></span><span class="line"><span class="cl">	<span class="o">^</span> <span class="s">&#39;librusthelloworld.dylib&#39;</span>
</span></span></code></pre></div><p>Create  <code>HelloWorld</code> as subclass of <code>Object</code> and on the class side add these three methods:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">ffiLibrary</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="o">^</span> <span class="nc">RustHelloWorldLibrary</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">getFFIRustHelloString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="o">^</span> <span class="bp">self</span> <span class="nf">ffiCall:</span> <span class="ss">#(</span> <span class="ss">char</span> <span class="ss">*</span> <span class="ss">get_hello_world</span> <span class="ss">#(</span> <span class="ss">)</span> <span class="ss">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">getHelloString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	&lt;<span class="k">primitive:</span> &#39;primitiveGetHelloString&#39; module: &#39;HelloWorldPlugin&#39;&gt;
</span></span><span class="line"><span class="cl">	<span class="bp">self</span> <span class="nf">primitiveFailed</span>
</span></span></code></pre></div><p>With that, you&rsquo;ll have accessors to the strings that come from the lib in the FFI case and from the primitive of your plugin in the other case:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">HelloWorld</span> <span class="nf">getFFIRustHelloString</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nc">HelloWorld</span> <span class="nf">getHelloString</span><span class="p">.</span>
</span></span></code></pre></div><p>Install ABBench for easy comparing bechmarks:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Metacello</span> <span class="nb">new</span>
</span></span><span class="line"><span class="cl">  <span class="nf">githubUser:</span> <span class="s">&#39;emdonahue&#39;</span> <span class="nf">project:</span> <span class="s">&#39;ABBench&#39;</span> <span class="nf">commitish:</span> <span class="s">&#39;master&#39;</span> <span class="nf">path:</span> <span class="s">&#39;&#39;</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">  <span class="nf">baseline:</span> <span class="s">&#39;ABBench&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">load</span><span class="p">.</span>
</span></span></code></pre></div><p>And run some on these two methods.</p>
<p>Here are the results is showing in a 2.5GHz Intel Quad-core i7 on macOS:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl">[ <span class="nc">HelloWorld</span> <span class="nf">getFFIRustHelloString</span> ] <span class="nf">bench</span><span class="p">.</span> 
</span></span><span class="line"><span class="cl"><span class="c">&#34;&#39;402334.199 per second&#39;&#34;</span>
</span></span><span class="line"><span class="cl">[ <span class="nc">HelloWorld</span> <span class="nf">getHelloString</span> ] <span class="nf">bench</span><span class="p">.</span>   
</span></span><span class="line"><span class="cl"><span class="c">&#34;&#39;22058210.074 per second&#39;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nc">ABBench</span> <span class="nf">bench:</span>[ <span class="nc">ABBench</span> 
</span></span><span class="line"><span class="cl">	<span class="nf">a:</span> [<span class="nc">HelloWorld</span> <span class="nf">getFFIRustHelloString</span>] 
</span></span><span class="line"><span class="cl">	<span class="nf">b:</span> [<span class="nc">HelloWorld</span> <span class="nf">getHelloString</span> ] ]<span class="p">.</span> 
</span></span><span class="line"><span class="cl"><span class="c">&#34;B is 3087.45% FASTER than A&#34;</span>
</span></span></code></pre></div><p><strong>Conclusion</strong></p>
<p>Extending the Pharo Virtual Machine with a plugin is a less-traveled road, but one that offers a lot of potential. Whether you are looking to increase performance or simplify the process of accessing specific functionality, plugins can offer a way to extend Smalltalk in a way that keeps your technology stack simple, elegant and powerful.</p>
<p>The steps involved in producing a plugin are not as straightforward as with FFI, but with a little time and effort, you can create very powerful plugins that add new functionality to your Pharo applications. We hope that by working through the development cycle of a <code>HelloWorldPlugin</code> as described in this article, you can get a sense of the process involved and gain a deeper understanding of the Pharo Virtual Machine.</p>
<p>Overall, extending the Pharo Virtual Machine with a plugin is an excellent way to maximize the potential of Smalltalk, and we hope this article has inspired you to take the next step in your Smalltalk development journey and unlocking potential.</p>
<p><strong>Acknowledgements</strong></p>
<p>A thank you note to the maintainers of the <code>paro-project/pharo-vm</code>, and Guille Polito in particular, which were kind enough to review and merge <a href="https://github.com/pharo-project/pharo-vm/pull/445">this modest PR</a> which allows to quickly test that <code>PharoVMMaker</code> is able to generate the source code for the list of plugins that you define.</p>
<p>And a very special mention to Pierre Misse-Chanabier which whom I had many conversations in june 2022 discovering how to get this done. Thanks a lot! None of this work would have been possible without your kind attention and explanations Pierre!</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/addPharoVMRepo.gif" alt=""" loading="lazy" /></figure><p><strong>Introduction</strong></p>
<p>When working with Smalltalk, you sometimes wish to access functionality that exists only in a specific library or technology that you cannot or simply don&rsquo;t want to reinvent. When you are in this scenario, two options come to mind:</p>
<ol>
<li>FFI, or Foreign Function Interface, which is a mechanism to enable a program written in one programming language to interact with code written in another programming language. Essentially, a &ldquo;bridge&rdquo; that allows them to communicate and exchange information with each other.</li>
<li>Somehow make the Smalltalk Virtual Machine to have more primitives that extend its default functionality to produce this communication back and forth, from the Smalltalk code and these other libraries.</li>
</ol>
<p>In developing terms, FFI is usually the fastest way to achieve this and, for Pharo, is extensibly covered in <a href="http://books.pharo.org/booklet-uffi/pdf/2020-02-12-uFFI-V1.0.1">Unified FFI - Calling Foreign Functions from Pharo</a> by Guillermo Polito, Stéphane Ducasse, Pablo Tesone and Ted Brunzie.</p>
<p>In this article, we&rsquo;ll delve into the less-traveled road of extending the Pharo Virtual Machine with an external plugin by creating a HelloWorldPlugin. With this, I want to provide some clarity that could demystify the process of making extensions in this way by understanding the steps currently needed to achieve this in Pharo 9, as per February 2023.</p>
<p><strong>Motivation</strong></p>
<p>The primary motivation behind extending the Pharo Virtual Machine with a plugin is often performance, but in some cases, it can also simplify the process of accessing specific functionality. For instance, if you have a favorite library that can only be used by Python or Lua, as a Smalltalker, you may be forced to create a bridge to these technologies, adding additional setup instructions and increasing the complexity of your application&rsquo;s technology stack. With the use of plugins, however, you can extend Smalltalk and keep the complexity of your tech stack to a minimum. After all, Smalltalk when used keeping an elegant software design, can be considered a tool for dominating complexity.</p>
<p>Back to performance, you will easily find cases where the FFI marshaling back and forth process might be considered too costly, and your Smalltalk application would find a more desirable alternative in getting dedicated primitives from a plugin to use the targeted functionality. You may be using plugins like FilePlugin, FloatArrayPlugin, SocketPlugin, and UnixOSProcessPlugin without even realizing it and, as you can imagine, they are all performance critical.</p>
<p>Let&rsquo;s work on your new <code>HelloWorldPlugin</code> now. Here is the outline of the development cycle for it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">┌───────────┐                                
</span></span><span class="line"><span class="cl">│MyPlugins/ │                                
</span></span><span class="line"><span class="cl">└──┬┬───────┘               Plugin image     
</span></span><span class="line"><span class="cl">   ││  ┌──────────────────┐       │          
</span></span><span class="line"><span class="cl">   └┼─▶│HelloWorldPlugin/ │◀──────┘          
</span></span><span class="line"><span class="cl">    │  └──────────────────┘                  
</span></span><span class="line"><span class="cl">    │  ┌──────────┐                          
</span></span><span class="line"><span class="cl">    └─▶│pharo-vm/ │◀──────────────┐          
</span></span><span class="line"><span class="cl">       └──────────┘               │          
</span></span><span class="line"><span class="cl">                       Code to produce builds
</span></span></code></pre></div><p><strong>Setup procedure</strong></p>
<p>You&rsquo;ll need to do this once:</p>
<ol>
<li>A directory where you&rsquo;ll use a Pharo image for coding your plugin. Let&rsquo;s refer to this as &ldquo;the plugin image&rdquo;.</li>
<li>A directory with a clone of the <a href="https://github.com:pharo-project/pharo-vm.git">pharo-vm project</a></li>
<li>In the plugin image, you&rsquo;ll install <code>BaselineOfVMMaker</code> which has all the infrastructure needed to produce the sources that you will later compile in a build process.</li>
<li>In the plugin image, you&rsquo;ll checkout the <code>Pharo9</code> branch from origin for compatibility with this guide.</li>
</ol>
<p><strong>Development procedure</strong></p>
<p>You&rsquo;ll iterate this part producing builds of your plugin until you get to the final version:</p>
<ol>
<li>In the plugin image, you&rsquo;ll locally create a new branch using that <code>Pharo9</code> branch. That is your plugin development starting point. For this exercise we&rsquo;ll create a this branch naming it <code>HelloWorldPlugin</code>.</li>
<li>In the plugin image, you&rsquo;ll add the code of the <code>HelloWorldPlugin</code> class and its methods.</li>
<li>In the plugin image, you&rsquo;ll update (needed only once) which the external plugins are going to get generated adding <code>HelloWorldPlugin</code> to the list and you&rsquo;ll commit that. Note: you will only pick to commit that change and not any other automated change that Iceberg might detect.</li>
</ol>
<p><strong>Building procedure</strong></p>
<ol>
<li>From the <code>MyPlugin</code> directory, you&rsquo;ll run the <code>cmake</code> command to configure the environment for compilation.</li>
<li>From the <code>MyPlugin/build</code> directory, you&rsquo;ll run the <code>make</code>  command to compile and produce the build.</li>
<li>From the <code>MyPlugin/pharo-vm</code> directory, you&rsquo;ll edit <code>plugins.cmake</code> to make it include in the build the generated sources of <code>HelloWorldPlugin</code>.</li>
<li>From the <code>MyPlugin</code> directory, you&rsquo;ll run the <code>cmake</code> command again to configure everything including the generated code of your plugin.</li>
<li>From the <code>MyPlugin</code> directory, you&rsquo;ll run the <code>make</code> command to compile producing the binaries.</li>
</ol>
<p><strong>Note</strong>: If you don&rsquo;t follow the sequence of the development cycle, you&rsquo;ll notice that <code>cmake</code> can easily run into problems by not finding the sources of <code>HelloWorldPlugin</code> automatically generated in <code>build/generated/64/plugins/src/HelloWorldPlugin</code>. The first round of <code>cmake</code> and <code>make</code> (steps 8 and 9) are not actually to produce the binaries you&rsquo;ll want, but only to have <code>make</code> producing the generated sources that you&rsquo;ll use <em>after</em> <code>plugins.cmake</code> gets edited to include your plugin and then built with step&rsquo;s 5 <code>make</code>.</p>
<p><strong>Setup</strong></p>
<p>Go to your favorite folder for your code and prepare a directory to work with this, here I&rsquo;ll call it <code>MyPlugins</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="nb">cd</span> ~/yourFavoriteCodeDirForThis
</span></span><span class="line"><span class="cl">$ mkdir MyPlugins
</span></span><span class="line"><span class="cl">$ <span class="nb">cd</span> MyPlugins
</span></span></code></pre></div><p>Clone the Pharo code for building Pharo VMs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git clone git@github.com:pharo-project/pharo-vm.git
</span></span></code></pre></div><p>Create the <code>HelloWorldPlugin</code> directory to have the image to produce the new code, so go ahead and grab a fresh pharo 9 and start it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ mkdir HelloWorldPlugin$ <span class="nb">pwd</span>
</span></span><span class="line"><span class="cl">...blah/MyPlugins/HelloWorldPlugin
</span></span><span class="line"><span class="cl">$  curl get.pharo.org/64/90 <span class="p">|</span> bash
</span></span><span class="line"><span class="cl">$  curl get.pharo.org/64/vm90 <span class="p">|</span> bash
</span></span><span class="line"><span class="cl">$ ./pharo
</span></span></code></pre></div><p>Off-topic, if you are like me, the first thing to do on fresh Pharo images is to tune the visuals by loading in a Playground:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Metacello</span> <span class="nb">new</span> 
</span></span><span class="line"><span class="cl">    <span class="nf">baseline:</span> <span class="s">&#39;PharoDawnTheme&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">repository:</span> <span class="s">&#39;github://sebastianconcept/PharoDawnTheme&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">load</span><span class="p">.</span>
</span></span></code></pre></div><p>Now, from this plugin image, add the repository from the cloned pharo-vm project using Iceberg and checkout the <code>Pharo9</code> branch:</p>
<p>Now, load <code>BaselineOfVMMaker</code>:
<img alt="Loading BaselineOfVMMaker" loading="lazy" src="https://blog.sebastiansastre.co/img/loadVMMakerBaseline.gif"></p>
<p>Next, from the <code>Pharo9</code> branch, create the new <code>HelloWorldPlugin</code> branch:
<img alt="Branch HelloWorldPlugin out of Pharo9" loading="lazy" src="https://blog.sebastiansastre.co/img/branchHelloWorldPlugin.gif"></p>
<p>And now we can finally add your plugin code.</p>
<p>Go to <code>SmartSyntaxInterpreterPlugin</code> class. This one will be the superclass of your plugin.</p>
<p><strong>Important</strong>: maintain <code>HelloWorldPlugin</code> in the <code>VMMaker-Plugins</code> package as required for automated code generation.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">SmartSyntaxInterpreterPlugin</span> <span class="nf">subclass:</span> <span class="ss">#HelloWorldPlugin</span>
</span></span><span class="line"><span class="cl">	<span class="nf">instanceVariableNames:</span> <span class="s">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">classVariableNames:</span> <span class="s">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">package:</span> <span class="s">&#39;VMMaker-Plugins&#39;</span>
</span></span></code></pre></div><p>In the <strong>class side</strong> of <code>HelloWorldPlugin</code> add the <code>moduleNameAndVersion</code> method:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">moduleNameAndVersion</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="o">^</span> <span class="bp">self</span> <span class="nf">moduleName</span> <span class="nf">,</span><span class="nc">Character</span> <span class="nf">space</span> <span class="nf">asString</span> <span class="nf">,</span> <span class="nc">Date</span> <span class="nf">today</span> <span class="nf">asString</span>
</span></span></code></pre></div><p>In the <strong>instance side</strong> of <code>HelloWorldPlugin</code> add the <code>primitiveGetHelloString</code> and <code>stringFromCString:</code> methods:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">primitiveGetHelloString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	&lt;<span class="k">export:</span> true&gt;
</span></span><span class="line"><span class="cl">	<span class="nv">interpreterProxy</span>
</span></span><span class="line"><span class="cl">		<span class="nf">pop:</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">		<span class="nf">thenPush:</span> (<span class="nv">interpreterProxy</span> <span class="nf">stringFromCString:</span>
</span></span><span class="line"><span class="cl">			 <span class="s">&#39;Hello from your HelloWorldPlugin.&#39;</span>)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">stringFromCString:</span> <span class="nv">aCString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c">&#34;Answer a new String copied from a null-terminated C string.
</span></span></span><span class="line"><span class="cl"><span class="c">    Caution: This may invoke the garbage collector.&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	&lt;<span class="k">var:</span> &#39;aCString&#39; type: #&#39;const char *&#39;&gt;
</span></span><span class="line"><span class="cl">	<span class="o">|</span><span class="nv"> len newString </span><span class="o">|</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="nv">len</span> <span class="o">:=</span> <span class="bp">self</span> <span class="nf">strlen:</span> <span class="nv">aCString</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">	<span class="nv">newString</span> <span class="o">:=</span> <span class="nv">interpreterProxy</span>
</span></span><span class="line"><span class="cl">		             <span class="nf">instantiateClass:</span> <span class="nv">interpreterProxy</span> <span class="nf">classString</span>
</span></span><span class="line"><span class="cl">		             <span class="nf">indexableSize:</span> <span class="nv">len</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">	<span class="nv">newString</span> <span class="nf">ifNil:</span> [ 
</span></span><span class="line"><span class="cl">		<span class="o">^</span> <span class="nv">interpreterProxy</span> <span class="nf">primitiveFailFor:</span> <span class="nc">PrimErrNoMemory</span> ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">	<span class="bp">self</span>
</span></span><span class="line"><span class="cl">		<span class="nf">strncpy:</span> (<span class="nv">interpreterProxy</span> <span class="nf">arrayValueOf:</span> <span class="nv">newString</span>)
</span></span><span class="line"><span class="cl">		<span class="o">_</span><span class="err">:</span> <span class="nv">aCString</span>
</span></span><span class="line"><span class="cl">		<span class="o">_</span><span class="err">:</span> <span class="nv">len</span><span class="p">.</span> <span class="c">&#34;(char *)strncpy()&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="o">^</span> <span class="nv">newString</span>
</span></span></code></pre></div><p>If you&rsquo;re wondering about that unusual looking code, that&rsquo;s <a href="http://wiki.squeak.org/squeak/slang">Slang</a>, I&rsquo;m not going to cover that here but is suffice for now to know that it&rsquo;s a code generation tool originally used in Squeak and now used to produce the Cog virtual machine used in Pharo. Slang converts Smalltalk code into C source code, which is then compiled to produce a virtual machine or, as you&rsquo;ll soon see, your <code>HelloWorldPlugin</code>. Slang basically helps to simplify the process of extending the Smalltalk virtual machine by providing an interface for generating multiplatform C code directly from Smalltalk code. This makes Smalltalk more maintainable and portable.</p>
<p>Done and said that, you&rsquo;re almost ready to commit. The only missing piece of development is to tell <code>VMMaker</code> to use your class to create an external plugin.</p>
<p>Browse the <code>PharoVMMaker&gt;&gt;generate:memoryManager:</code> method and add the name <code>HelloWorldPlugin</code> to the array of <code>external</code> plugins.</p>
<p>The full method should be:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">generate:</span> <span class="nv">interpreterClass</span> <span class="nf">memoryManager:</span> <span class="nv">memoryManager</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="o">|</span><span class="nv"> platformDirectory </span><span class="o">|</span>
</span></span><span class="line"><span class="cl">	<span class="nc">Author</span> <span class="nf">useAuthor:</span> <span class="s">&#39;vmMaker&#39;</span> <span class="nf">during:</span> [ 
</span></span><span class="line"><span class="cl">		<span class="nc">VMMakerConfiguration</span> <span class="nf">initializeForPharo</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">		(<span class="nv">interpreterClass</span> <span class="nf">bindingOf:</span> <span class="ss">#COGMTVM</span>) <span class="nf">value:</span> <span class="bp">false</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">		<span class="nv">platformDirectory</span> <span class="o">:=</span> <span class="bp">self</span> <span class="nf">platformDirectoryFor:</span> <span class="nv">memoryManager</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">		[ 
</span></span><span class="line"><span class="cl">		(<span class="nc">VMMaker</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">makerFor:</span> <span class="nv">interpreterClass</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">and:</span> <span class="nc">StackToRegisterMappingCogit</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">with:</span> { 
</span></span><span class="line"><span class="cl">					 <span class="ss">#COGMTVM</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="bp">false</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="ss">#ObjectMemory</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="nv">memoryManager</span> <span class="nf">name</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="ss">#MULTIPLEBYTECODESETS</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="bp">true</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="ss">#bytecodeTableInitializer</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">					 <span class="ss">#initializeBytecodeTableForSqueakV3PlusClosuresSistaV1Hybrid</span> }
</span></span><span class="line"><span class="cl">			 <span class="nf">to:</span> <span class="nv">platformDirectory</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">platformDir:</span> <span class="nv">platformDirectory</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">including:</span> <span class="ss">#(</span>  <span class="ss">)</span>
</span></span><span class="line"><span class="cl">			 <span class="nf">configuration:</span> <span class="nc">VMMakerConfiguration</span>)
</span></span><span class="line"><span class="cl">			<span class="nf">stopOnErrors:</span> <span class="nv">stopOnErrors</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="nf">internal:</span> <span class="ss">#(</span>  <span class="ss">)</span>
</span></span><span class="line"><span class="cl">			<span class="nf">external:</span>
</span></span><span class="line"><span class="cl">				<span class="ss">#(</span> <span class="ss">FilePlugin</span> <span class="ss">SurfacePlugin</span> <span class="ss">FloatArrayPlugin</span> <span class="ss">HelloWorldPlugin</span> <span class="ss">)</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="nf">generateInterpreterFile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="nf">generateCogitFiles</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="nf">generateExternalPlugins</span> ] <span class="nf">valueSupplyingAnswer:</span> <span class="bp">true</span> ]
</span></span></code></pre></div><p>Now you can commit your changes completing the <em>development procedure</em> and ready to move on to the <em>building procedure</em>.</p>
<p><img alt="Commit the HelloWorldPlugin code in the HelloWorldPlugin branch" loading="lazy" src="https://blog.sebastiansastre.co/img/codeHelloWorldPlugin.gif"></p>
<p>For the building procedure and to evade a chicken-egg kind of problem, we&rsquo;re going to do a first round of running <code>cmake</code> and <code>make</code> commands that will autogenerate the C source code based on your methods written in slang and leave these source files ready to use at <code>build/generated/64/plugins/src/HelloWorldPlugin</code> (and likely also <code>build/generated/32/...</code>).</p>
<p>Go to your <code>MyPlugins/</code> directory and run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cmake -S pharo-vm -B build
</span></span></code></pre></div><p>After this run, you should see a list of plugins <em>without</em> <code>HelloWorldPlugin</code>, this is expected at this time. Also expected, is that you will not have yet the <code>build/generated/</code> directory. We are going to produce it in the next command.</p>
<p>From <code>MyPlugins/build</code> run the <code>make</code> command to generate and compile everything for the first time:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ make install
</span></span></code></pre></div><p>At the end of this process, you should see that <code>build/generated/64/plugins/src/HelloWorldPlugin</code> does exists now.</p>
<p>Time to tell cmake that it can configure a build including your generated sources. With your favorite editor open for editing the <code>MyPlugins/pharo-vm/plugins.cmake</code> file and add this above or below the configuration for <code>Surface Plugin</code>. We&rsquo;re basically going to tell <code>cmake</code> that it should do with <code>HelloWorldPlugin</code> the same as with <code>SurfacePlugin</code>, build it from the sources as an external plugin.</p>
<p>Your edited <code>plugins.cmake</code> now should look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl"><span class="err">...</span> <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="c"># HelloWorld Plugin
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">add_vm_plugin</span><span class="p">(</span><span class="s">HelloWorldPlugin</span> 
</span></span><span class="line"><span class="cl">	<span class="o">${</span><span class="nv">PHARO_CURRENT_GENERATED</span><span class="o">}</span><span class="s">/plugins/src/HelloWorldPlugin/HelloWorldPlugin.c</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="c"># Surface Plugin
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">add_vm_plugin</span><span class="p">(</span><span class="s">SurfacePlugin</span> 
</span></span><span class="line"><span class="cl">	<span class="o">${</span><span class="nv">PHARO_CURRENT_GENERATED</span><span class="o">}</span><span class="s">/plugins/src/SurfacePlugin/SurfacePlugin.c</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="c"># FloatArray Plugin
</span></span></span><span class="line"><span class="cl"><span class="c">#
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">add_vm_plugin</span><span class="p">(</span><span class="s">FloatArrayPlugin</span> 
</span></span><span class="line"><span class="cl">	<span class="o">${</span><span class="nv">PHARO_CURRENT_GENERATED</span><span class="o">}</span><span class="s">/plugins/src/FloatArrayPlugin/FloatArrayPlugin.c</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">...
</span></span></span></code></pre></div><p>Next, from <code>MyPlugins</code> directory, run <code>cmake</code> again:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cmake -S pharo-vm -B build
</span></span></code></pre></div><p>And at the end, you should find it lists your plugin:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">... 
</span></span><span class="line"><span class="cl">   B2DPlugin
</span></span><span class="line"><span class="cl">   BitBltPlugin
</span></span><span class="line"><span class="cl">   DSAPrims
</span></span><span class="line"><span class="cl">   FileAttributesPlugin
</span></span><span class="line"><span class="cl">   FilePlugin
</span></span><span class="line"><span class="cl">   FloatArrayPlugin
</span></span><span class="line"><span class="cl">   HelloWorldPlugin
</span></span><span class="line"><span class="cl">   JPEGReadWriter2Plugin
</span></span><span class="line"><span class="cl">	...
</span></span></code></pre></div><p>And finally, to build your plugin, from <code>MyPlugins/build</code> run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ make install
</span></span></code></pre></div><p>Once the compilation finishes, we can check the results.</p>
<p>As I&rsquo;m doing this on <code>macOS</code> the resulting directory for the binaries is <code>build/vm/Debug/Pharo.app/Contents/MacOS/Plugins</code> .</p>
<p>And there it is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ ls -la build/vm/Debug/Pharo.app/Contents/MacOS/Plugins 
</span></span><span class="line"><span class="cl">total <span class="m">83232</span>
</span></span><span class="line"><span class="cl">drwxr-xr-x  <span class="m">54</span> seb  staff     <span class="m">1728</span> Feb <span class="m">10</span> 19:01 .
</span></span><span class="line"><span class="cl">drwxr-xr-x   <span class="m">4</span> seb  staff      <span class="m">128</span> Feb <span class="m">10</span> 19:01 ..
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff   <span class="m">135352</span> Feb <span class="m">10</span> 19:01 libB2DPlugin.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">97864</span> Feb <span class="m">10</span> 19:01 libBitBltPlugin.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">35952</span> Feb <span class="m">10</span> 19:01 libDSAPrims.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">77256</span> Feb <span class="m">10</span> 19:01 libFileAttributesPlugin.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">88800</span> Feb <span class="m">10</span> 19:01 libFilePlugin.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">22352</span> Feb <span class="m">10</span> 19:01 libFloatArrayPlugin.dylib
</span></span><span class="line"><span class="cl">-rwxr-xr-x   <span class="m">1</span> seb  staff    <span class="m">17624</span> Feb <span class="m">10</span> 19:01 libHelloWorldPlugin.dylib
</span></span></code></pre></div><p>Let&rsquo;s see it in action:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ build/vm/Debug/Pharo.app/Contents/MacOS/Pharo build/vmmaker/image/Pharo10.0.1-0-64bit-0542643.image --interactive
</span></span></code></pre></div><p>Create an <code>Object</code> subclass:  <code>HelloWorld</code> class and add this instance method:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">getHelloString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	&lt;<span class="k">primitive:</span> &#39;primitiveGetHelloString&#39; module: &#39;HelloWorldPlugin&#39;&gt;
</span></span><span class="line"><span class="cl">	<span class="bp">self</span> <span class="nf">primitiveFailed</span>
</span></span></code></pre></div><p>And test it from this snippet:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">listLoadedModules</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nc">Smalltalk</span> <span class="nf">vm</span> <span class="nf">listBuiltinModules</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">hello</span> <span class="o">:=</span> <span class="nc">HelloWorld</span> <span class="nb">new</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">hello</span> <span class="nf">getHelloString</span><span class="p">.</span> 
</span></span></code></pre></div><p><img alt="Using the plugin primitive from Smalltalk" loading="lazy" src="https://blog.sebastiansastre.co/img/testingHelloWorld.gif"></p>
<p>With all going well, in your workspace you&rsquo;ll be getting the string produced by your new primitive.</p>
<p>Notice that is you check <code>listLoadedModules</code> before using <code>getHelloString</code> your plugin is not going to be loaded but if you check after using it for the first time, you&rsquo;ll find it there showing that Pharo lazy loads the plugins.</p>
<p><strong>Benchmarking</strong></p>
<p>As it gets revealed by now, creating a Pharo Smalltalk plugin requires significant effort, so to get a sense of proportions of what you get for it, lets compare how your plugin performs against a C shared library.</p>
<p>Rust is known to have a general computing performance almost at par with C so I&rsquo;ve built this simple hello world lib <a href="https://github.com/sebastianconcept/librusthelloworld">here</a> that can be used to measure this. The README.md file has instructions to build the library.</p>
<p>Go ahead in and build it for release.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git clone git@github.com:sebastianconcept/librusthelloworld.git
</span></span><span class="line"><span class="cl">$ cargo build --release
</span></span></code></pre></div><p>You will find the built library in:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ target/release/librusthelloworld.dylib
</span></span></code></pre></div><p>Create a symlink in the image dir so it can find <code>target/release/librusthelloworld.dylib</code>.</p>
<p>Create <code>RustHelloWorldLibrary</code> as subclass of <code>FFILibrary</code> and add this method in the <em>instance side</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">macModuleName</span>
</span></span><span class="line"><span class="cl">	<span class="o">^</span> <span class="s">&#39;librusthelloworld.dylib&#39;</span>
</span></span></code></pre></div><p>Create  <code>HelloWorld</code> as subclass of <code>Object</code> and on the class side add these three methods:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">ffiLibrary</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="o">^</span> <span class="nc">RustHelloWorldLibrary</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">getFFIRustHelloString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="o">^</span> <span class="bp">self</span> <span class="nf">ffiCall:</span> <span class="ss">#(</span> <span class="ss">char</span> <span class="ss">*</span> <span class="ss">get_hello_world</span> <span class="ss">#(</span> <span class="ss">)</span> <span class="ss">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nf">getHelloString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	&lt;<span class="k">primitive:</span> &#39;primitiveGetHelloString&#39; module: &#39;HelloWorldPlugin&#39;&gt;
</span></span><span class="line"><span class="cl">	<span class="bp">self</span> <span class="nf">primitiveFailed</span>
</span></span></code></pre></div><p>With that, you&rsquo;ll have accessors to the strings that come from the lib in the FFI case and from the primitive of your plugin in the other case:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">HelloWorld</span> <span class="nf">getFFIRustHelloString</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nc">HelloWorld</span> <span class="nf">getHelloString</span><span class="p">.</span>
</span></span></code></pre></div><p>Install ABBench for easy comparing bechmarks:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Metacello</span> <span class="nb">new</span>
</span></span><span class="line"><span class="cl">  <span class="nf">githubUser:</span> <span class="s">&#39;emdonahue&#39;</span> <span class="nf">project:</span> <span class="s">&#39;ABBench&#39;</span> <span class="nf">commitish:</span> <span class="s">&#39;master&#39;</span> <span class="nf">path:</span> <span class="s">&#39;&#39;</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">  <span class="nf">baseline:</span> <span class="s">&#39;ABBench&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">load</span><span class="p">.</span>
</span></span></code></pre></div><p>And run some on these two methods.</p>
<p>Here are the results is showing in a 2.5GHz Intel Quad-core i7 on macOS:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl">[ <span class="nc">HelloWorld</span> <span class="nf">getFFIRustHelloString</span> ] <span class="nf">bench</span><span class="p">.</span> 
</span></span><span class="line"><span class="cl"><span class="c">&#34;&#39;402334.199 per second&#39;&#34;</span>
</span></span><span class="line"><span class="cl">[ <span class="nc">HelloWorld</span> <span class="nf">getHelloString</span> ] <span class="nf">bench</span><span class="p">.</span>   
</span></span><span class="line"><span class="cl"><span class="c">&#34;&#39;22058210.074 per second&#39;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nc">ABBench</span> <span class="nf">bench:</span>[ <span class="nc">ABBench</span> 
</span></span><span class="line"><span class="cl">	<span class="nf">a:</span> [<span class="nc">HelloWorld</span> <span class="nf">getFFIRustHelloString</span>] 
</span></span><span class="line"><span class="cl">	<span class="nf">b:</span> [<span class="nc">HelloWorld</span> <span class="nf">getHelloString</span> ] ]<span class="p">.</span> 
</span></span><span class="line"><span class="cl"><span class="c">&#34;B is 3087.45% FASTER than A&#34;</span>
</span></span></code></pre></div><p><strong>Conclusion</strong></p>
<p>Extending the Pharo Virtual Machine with a plugin is a less-traveled road, but one that offers a lot of potential. Whether you are looking to increase performance or simplify the process of accessing specific functionality, plugins can offer a way to extend Smalltalk in a way that keeps your technology stack simple, elegant and powerful.</p>
<p>The steps involved in producing a plugin are not as straightforward as with FFI, but with a little time and effort, you can create very powerful plugins that add new functionality to your Pharo applications. We hope that by working through the development cycle of a <code>HelloWorldPlugin</code> as described in this article, you can get a sense of the process involved and gain a deeper understanding of the Pharo Virtual Machine.</p>
<p>Overall, extending the Pharo Virtual Machine with a plugin is an excellent way to maximize the potential of Smalltalk, and we hope this article has inspired you to take the next step in your Smalltalk development journey and unlocking potential.</p>
<p><strong>Acknowledgements</strong></p>
<p>A thank you note to the maintainers of the <code>paro-project/pharo-vm</code>, and Guille Polito in particular, which were kind enough to review and merge <a href="https://github.com/pharo-project/pharo-vm/pull/445">this modest PR</a> which allows to quickly test that <code>PharoVMMaker</code> is able to generate the source code for the list of plugins that you define.</p>
<p>And a very special mention to Pierre Misse-Chanabier which whom I had many conversations in june 2022 discovering how to get this done. Thanks a lot! None of this work would have been possible without your kind attention and explanations Pierre!</p>
]]></content:encoded>
    </item>
    <item>
      <title>PharoDawnTheme updated</title>
      <link>https://blog.sebastiansastre.co/posts/pharodawntheme-updated/</link>
      <pubDate>Wed, 01 Feb 2023 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/pharodawntheme-updated/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/PharoDawnTheme.png" type="image/png" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/PharoDawnTheme.png" alt=""" loading="lazy" /></figure><p>Today, I&rsquo;m excited to announce a new release of <a href="https://github.com/sebastianconcept/pharodawntheme">PharoDawnTheme</a>, this dark warm color theme for the Pharo Smalltalk IDE.</p>
<p>This theme is available for Pharo 9 and 10  and can be found in the following URL: <a href="https://github.com/sebastianconcept/pharodawntheme">https://github.com/sebastianconcept/pharodawntheme</a>.</p>
<p>To install it either in Pharo 9 or 10 you can evaluate this in a playground:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Metacello</span> <span class="nb">new</span> 
</span></span><span class="line"><span class="cl">	<span class="nf">baseline:</span> <span class="s">&#39;PharoDawnTheme&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">repository:</span> <span class="s">&#39;github://sebastianconcept/PharoDawnTheme&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">load</span><span class="p">.</span>
</span></span></code></pre></div><p>PharoDawnTheme is designed to make the development experience in Pharo more enjoyable and efficient. The theme has been optimized for readability and usability, giving developers a more comfortable and efficient development environment resolving some issues found in other themes.</p>
<p>This Dawn theme incorporates modern aesthetics, making your Pharo code look gentle and inviting and removing exaggerated and improperly distracting highlights.</p>
<p>I believe that Pharo developers deserve an environment that gets out of their way and allows them to focus on the goals and concepts they aim to master with their code. With PharoDawnTheme, I&rsquo;ve tried to provide a theme that enhances the software development experience as much as a color theme can.</p>
<p>We invite you to try out this theme and see how you feel it.</p>
<p>And if you find any issues don&rsquo;t hesitate in opening one in GitHub.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/PharoDawnTheme.png" alt=""" loading="lazy" /></figure><p>Today, I&rsquo;m excited to announce a new release of <a href="https://github.com/sebastianconcept/pharodawntheme">PharoDawnTheme</a>, this dark warm color theme for the Pharo Smalltalk IDE.</p>
<p>This theme is available for Pharo 9 and 10  and can be found in the following URL: <a href="https://github.com/sebastianconcept/pharodawntheme">https://github.com/sebastianconcept/pharodawntheme</a>.</p>
<p>To install it either in Pharo 9 or 10 you can evaluate this in a playground:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Metacello</span> <span class="nb">new</span> 
</span></span><span class="line"><span class="cl">	<span class="nf">baseline:</span> <span class="s">&#39;PharoDawnTheme&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">repository:</span> <span class="s">&#39;github://sebastianconcept/PharoDawnTheme&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">load</span><span class="p">.</span>
</span></span></code></pre></div><p>PharoDawnTheme is designed to make the development experience in Pharo more enjoyable and efficient. The theme has been optimized for readability and usability, giving developers a more comfortable and efficient development environment resolving some issues found in other themes.</p>
<p>This Dawn theme incorporates modern aesthetics, making your Pharo code look gentle and inviting and removing exaggerated and improperly distracting highlights.</p>
<p>I believe that Pharo developers deserve an environment that gets out of their way and allows them to focus on the goals and concepts they aim to master with their code. With PharoDawnTheme, I&rsquo;ve tried to provide a theme that enhances the software development experience as much as a color theme can.</p>
<p>We invite you to try out this theme and see how you feel it.</p>
<p>And if you find any issues don&rsquo;t hesitate in opening one in GitHub.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Mapless repositories with UnQLite backends now can be on RAM</title>
      <link>https://blog.sebastiansastre.co/posts/mapless-repositories-with-unqlite-backends-now-can-be-on-ram/</link>
      <pubDate>Fri, 15 Apr 2022 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/mapless-repositories-with-unqlite-backends-now-can-be-on-ram/</guid>
      <description><![CDATA[<p>Just a quick update to mention that I&rsquo;ve merged in <code>develop</code> a pull request that will add the capability to <a href="https://github.com/sebastianconcept/Mapless/issues/95">work with Mapless using  UnQLite in memory</a>.</p>
<p>The use case is mostly for single-image caching. When you want to cache  data but keep the image lean, that&rsquo;s when you can use this setup.</p>
<p>Have in mind that when you save a mapless in one image, you will <em>not</em> find it from any other one, only from the one that originated that. You also need to be conservative with the instance of the repository because only one UnQLite client will have access to the data you saved on it.</p>
<p>Of course if you need to access from many repository instances and many images, you can use the regular file-based one.</p>
<p>For reference, here is the snippet I was using while developing this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nv">dbFilename</span> <span class="o">:=</span> <span class="nc">FileSystem</span> <span class="nf">workingDirectory</span> <span class="nf">/</span> <span class="s">&#39;bench.db&#39;</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">dbFilename</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">dbFilename</span> <span class="nf">deleteIfAbsent:</span> [  ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;File-based repo&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">repository</span> <span class="o">:=</span> <span class="nc">MaplessUnQLiteRepository</span> <span class="nf">for:</span> <span class="nv">dbFilename</span> <span class="nf">pathString</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;In RAM memory repo&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">repository</span> <span class="o">:=</span> <span class="nc">MaplessUnQLiteRepository</span> <span class="nf">inMemory</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;To shut down cleanly&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">repository</span> <span class="nf">shutDown</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;Run the benchmark&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nc">MaplessUnQLiteBenchmark</span> <span class="nf">runOn:</span> <span class="nv">repository</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;Manually test saving and retrieving&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">guy</span> <span class="o">:=</span> <span class="nc">DummyPerson</span> <span class="nb">new</span>
</span></span><span class="line"><span class="cl">	<span class="nf">firstName:</span> <span class="s">&#39;john&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">lastName:</span> <span class="s">&#39;q&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">yourself</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;Save a mapless&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">repository</span> <span class="nf">save:</span> <span class="nv">guy</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;Monitor its ids (to access it from another image and things like that)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">id</span> <span class="o">:=</span> <span class="nv">guy</span> <span class="nf">id</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;Retrieve that mapless&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">repository</span> <span class="nf">findOne:</span> <span class="nc">DummyPerson</span> <span class="nf">atId:</span> <span class="s">&#39;ep3affz7h8ecjv3doz91ww1ci&#39;</span><span class="p">.</span>
</span></span></code></pre></div><p>When I ran the benchmarks I&rsquo;ve got:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Benchmarking Mapless on UnQLite in memory...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Saved 1000 instances of MaplessDummyPerson in: 58 ms (~17294 saves per second)
</span></span><span class="line"><span class="cl">Read 1000 instances of MaplessDummyPerson in: 27 ms (~37626 reads per second)
</span></span><span class="line"><span class="cl">Saved 10000 instances of MaplessDummyPerson in: 551 ms (~18142 saves per second)
</span></span><span class="line"><span class="cl">Read 10000 instances of MaplessDummyPerson in: 265 ms (~37698 reads per second)
</span></span><span class="line"><span class="cl">Saved 1000 instances of MaplessDummyPerson and MaplessDummyUser in: 132 ms (~7577 saves per second)
</span></span><span class="line"><span class="cl">Read 1000 instances of MaplessDummyPerson and MaplessDummyUser in: 39 ms (~25448 reads per second)
</span></span><span class="line"><span class="cl">Saved 10000 instances of MaplessDummyPerson and MaplessDummyUser in: 1328 ms (~7527 saves per second)
</span></span><span class="line"><span class="cl">Read 10000 instances of MaplessDummyPerson and MaplessDummyUser in: 406 ms (~24616 reads per second)
</span></span></code></pre></div><p>And here is how <code>DummyPerson</code> and <code>DummyUser</code> mapless get created:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nv">guy</span> <span class="o">:=</span> <span class="nc">DummyPerson</span> <span class="nb">new</span>
</span></span><span class="line"><span class="cl">	<span class="nf">firstName:</span> <span class="nc">Character</span> <span class="nf">alphabet</span> <span class="nf">shuffled</span> <span class="nf">anyOne</span> <span class="nf">asString</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">lastName:</span> <span class="nc">Character</span> <span class="nf">alphabet</span> <span class="nf">shuffled</span> <span class="nf">anyOne</span> <span class="nf">asString</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">yourself</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">user</span> <span class="o">:=</span> <span class="nc">DummyUser</span> <span class="nb">new</span>
</span></span><span class="line"><span class="cl">	<span class="nf">username:</span> <span class="nv">guy</span> <span class="nf">firstName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">person:</span> <span class="nv">guy</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">yourself</span><span class="p">.</span>
</span></span></code></pre></div><p>Happy Eastern to everyone.</p>
]]></description>
      <content:encoded><![CDATA[<p>Just a quick update to mention that I&rsquo;ve merged in <code>develop</code> a pull request that will add the capability to <a href="https://github.com/sebastianconcept/Mapless/issues/95">work with Mapless using  UnQLite in memory</a>.</p>
<p>The use case is mostly for single-image caching. When you want to cache  data but keep the image lean, that&rsquo;s when you can use this setup.</p>
<p>Have in mind that when you save a mapless in one image, you will <em>not</em> find it from any other one, only from the one that originated that. You also need to be conservative with the instance of the repository because only one UnQLite client will have access to the data you saved on it.</p>
<p>Of course if you need to access from many repository instances and many images, you can use the regular file-based one.</p>
<p>For reference, here is the snippet I was using while developing this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nv">dbFilename</span> <span class="o">:=</span> <span class="nc">FileSystem</span> <span class="nf">workingDirectory</span> <span class="nf">/</span> <span class="s">&#39;bench.db&#39;</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">dbFilename</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">dbFilename</span> <span class="nf">deleteIfAbsent:</span> [  ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;File-based repo&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">repository</span> <span class="o">:=</span> <span class="nc">MaplessUnQLiteRepository</span> <span class="nf">for:</span> <span class="nv">dbFilename</span> <span class="nf">pathString</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;In RAM memory repo&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">repository</span> <span class="o">:=</span> <span class="nc">MaplessUnQLiteRepository</span> <span class="nf">inMemory</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;To shut down cleanly&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">repository</span> <span class="nf">shutDown</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;Run the benchmark&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nc">MaplessUnQLiteBenchmark</span> <span class="nf">runOn:</span> <span class="nv">repository</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;Manually test saving and retrieving&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">guy</span> <span class="o">:=</span> <span class="nc">DummyPerson</span> <span class="nb">new</span>
</span></span><span class="line"><span class="cl">	<span class="nf">firstName:</span> <span class="s">&#39;john&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">lastName:</span> <span class="s">&#39;q&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">yourself</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;Save a mapless&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">repository</span> <span class="nf">save:</span> <span class="nv">guy</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;Monitor its ids (to access it from another image and things like that)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">id</span> <span class="o">:=</span> <span class="nv">guy</span> <span class="nf">id</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#34;Retrieve that mapless&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">repository</span> <span class="nf">findOne:</span> <span class="nc">DummyPerson</span> <span class="nf">atId:</span> <span class="s">&#39;ep3affz7h8ecjv3doz91ww1ci&#39;</span><span class="p">.</span>
</span></span></code></pre></div><p>When I ran the benchmarks I&rsquo;ve got:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Benchmarking Mapless on UnQLite in memory...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Saved 1000 instances of MaplessDummyPerson in: 58 ms (~17294 saves per second)
</span></span><span class="line"><span class="cl">Read 1000 instances of MaplessDummyPerson in: 27 ms (~37626 reads per second)
</span></span><span class="line"><span class="cl">Saved 10000 instances of MaplessDummyPerson in: 551 ms (~18142 saves per second)
</span></span><span class="line"><span class="cl">Read 10000 instances of MaplessDummyPerson in: 265 ms (~37698 reads per second)
</span></span><span class="line"><span class="cl">Saved 1000 instances of MaplessDummyPerson and MaplessDummyUser in: 132 ms (~7577 saves per second)
</span></span><span class="line"><span class="cl">Read 1000 instances of MaplessDummyPerson and MaplessDummyUser in: 39 ms (~25448 reads per second)
</span></span><span class="line"><span class="cl">Saved 10000 instances of MaplessDummyPerson and MaplessDummyUser in: 1328 ms (~7527 saves per second)
</span></span><span class="line"><span class="cl">Read 10000 instances of MaplessDummyPerson and MaplessDummyUser in: 406 ms (~24616 reads per second)
</span></span></code></pre></div><p>And here is how <code>DummyPerson</code> and <code>DummyUser</code> mapless get created:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nv">guy</span> <span class="o">:=</span> <span class="nc">DummyPerson</span> <span class="nb">new</span>
</span></span><span class="line"><span class="cl">	<span class="nf">firstName:</span> <span class="nc">Character</span> <span class="nf">alphabet</span> <span class="nf">shuffled</span> <span class="nf">anyOne</span> <span class="nf">asString</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">lastName:</span> <span class="nc">Character</span> <span class="nf">alphabet</span> <span class="nf">shuffled</span> <span class="nf">anyOne</span> <span class="nf">asString</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">yourself</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="nv">user</span> <span class="o">:=</span> <span class="nc">DummyUser</span> <span class="nb">new</span>
</span></span><span class="line"><span class="cl">	<span class="nf">username:</span> <span class="nv">guy</span> <span class="nf">firstName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">person:</span> <span class="nv">guy</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">yourself</span><span class="p">.</span>
</span></span></code></pre></div><p>Happy Eastern to everyone.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Mapless is online again</title>
      <link>https://blog.sebastiansastre.co/posts/mapless-is-online-again/</link>
      <pubDate>Sun, 10 Apr 2022 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/mapless-is-online-again/</guid>
      <description><![CDATA[<p>After quite some time not having updates on <a href="https://github.com/sebastianconcept/Mapless">Mapless</a>, I&rsquo;ve invested in getting it working for latests <a href="https://pharo.org">Pharo</a> versions and incorporating and maturing its API and main features.</p>
<p>Today&rsquo;s best Mapless version is <code>v0.5.0-alpha</code> and the remarks are:</p>
<ul>
<li>MongoDB backend can now connect to <a href="https://github.com/sebastianconcept/Mapless/issues/61">Replica Sets in a fault tolerant way</a></li>
<li>On MongoDB Mapless can be <a href="https://github.com/sebastianconcept/Mapless/issues/54">read from secondaries</a> and setting default and custom <a href="https://github.com/sebastianconcept/Mapless/issues/76">read</a> and write concerns.</li>
<li>Updated its <a href="https://github.com/sebastianconcept/Mapless/issues/79">PostgreSQL backend</a> using <a href="https://github.com/svenvc">Sven</a>&rsquo;s client <a href="https://github.com/svenvc/P3">P3</a></li>
<li><a href="https://github.com/sebastianconcept/Mapless/issues/73">Updated its Redis backend</a> by using <a href="https://github.com/mumez">Masashi</a>&rsquo;s <a href="https://github.com/mumez/RediStick">RediStick</a></li>
<li>Incorporated <a href="https://github.com/sebastianconcept/Mapless/issues/84">UnQLite backend</a> using <a href="https://github.com/mumez">Masashi</a>&rsquo;s <a href="https://github.com/mumez/PunQLite">PunQLite</a>.</li>
</ul>
<p>I can&rsquo;t miss taking the chance here to send a big thanks for the PRs and conversations and all the code reviews and general input to <a href="https://github.com/emaringolo">Esteban Maringolo</a>. You are a very healty influence buddy!</p>
<p>Also, to notice that last week I&rsquo;ve seen <a href="https://github.com/noha">Norbert</a>&rsquo;s announcement of updates in <a href="https://github.com/pharo-nosql/OmniBase">OmniBase</a> and I&rsquo;ve started to play with it. And with <a href="https://github.com/pharo-rdbms/Pharo-SQLite3">SQLite</a>. The idea was to evaluate them as possible next backends because there are use cases in which they can be a good fit.</p>
<p>In general, Mapless value proposition reveals itself as an interesting support for when you mostly want to self-host your data without being locked to a vendor.</p>
<p>For example, assuming your code was not entangled with backend&rsquo;s custom features and if you keep the devops and operations issues aside, your plan for migrating from MongoDB to PostgreSQL and vice versa, could be:</p>
<ul>
<li>Instantiating the new repositories used by your app.</li>
<li>Provide a way for your Mapless to have its properties properly indexed for query optimization.</li>
<li>Saving the data without further transformations.</li>
<li>Rewriting your app&rsquo;s queries.</li>
</ul>
<p>That&rsquo;s pretty much it.</p>
<p>With Mapless strategic value your app stays easy to work with and resilient and more adaptable to future changes.</p>
<p>Example, lets assume we add Firebase and DynamoDB support for Maplees. You decide to implement your app on one of these and use them while convenient, but later you migrate from it because the platform policies and economic value turned out to misalign with your business. With Mapless, your capacity for executing such decision would not be as dramatic as it would for other tech-stacks.</p>
<p>For more about the project&rsquo;s direction, you can take a look at the <a href="https://github.com/sebastianconcept/Mapless/issues">current</a> and <a href="https://github.com/sebastianconcept/Mapless/issues?q=is%3Aissue+is%3Aclosed">resolved</a> issues in GitHub. Also, its <a href="https://github.com/sebastianconcept/Mapless/blob/develop/changelog.md">changelog</a> is also fairly well maintained as I&rsquo;ve tried not to miss anything important there.</p>
<p>And I&rsquo;ve started this <a href="https://www.reddit.com/r/mapless_data/">Reddit community</a> to have a forum for the project&rsquo;s questions and as an effort to ease adoption.</p>
<p>You might also find me online in <a href="https://discord.com/invite/QewZMZa">Pharo&rsquo;s Discord server</a> where there is a <code>#database</code> channel for all Pharo persistence related conversations and I&rsquo;ll will help as I can.</p>
]]></description>
      <content:encoded><![CDATA[<p>After quite some time not having updates on <a href="https://github.com/sebastianconcept/Mapless">Mapless</a>, I&rsquo;ve invested in getting it working for latests <a href="https://pharo.org">Pharo</a> versions and incorporating and maturing its API and main features.</p>
<p>Today&rsquo;s best Mapless version is <code>v0.5.0-alpha</code> and the remarks are:</p>
<ul>
<li>MongoDB backend can now connect to <a href="https://github.com/sebastianconcept/Mapless/issues/61">Replica Sets in a fault tolerant way</a></li>
<li>On MongoDB Mapless can be <a href="https://github.com/sebastianconcept/Mapless/issues/54">read from secondaries</a> and setting default and custom <a href="https://github.com/sebastianconcept/Mapless/issues/76">read</a> and write concerns.</li>
<li>Updated its <a href="https://github.com/sebastianconcept/Mapless/issues/79">PostgreSQL backend</a> using <a href="https://github.com/svenvc">Sven</a>&rsquo;s client <a href="https://github.com/svenvc/P3">P3</a></li>
<li><a href="https://github.com/sebastianconcept/Mapless/issues/73">Updated its Redis backend</a> by using <a href="https://github.com/mumez">Masashi</a>&rsquo;s <a href="https://github.com/mumez/RediStick">RediStick</a></li>
<li>Incorporated <a href="https://github.com/sebastianconcept/Mapless/issues/84">UnQLite backend</a> using <a href="https://github.com/mumez">Masashi</a>&rsquo;s <a href="https://github.com/mumez/PunQLite">PunQLite</a>.</li>
</ul>
<p>I can&rsquo;t miss taking the chance here to send a big thanks for the PRs and conversations and all the code reviews and general input to <a href="https://github.com/emaringolo">Esteban Maringolo</a>. You are a very healty influence buddy!</p>
<p>Also, to notice that last week I&rsquo;ve seen <a href="https://github.com/noha">Norbert</a>&rsquo;s announcement of updates in <a href="https://github.com/pharo-nosql/OmniBase">OmniBase</a> and I&rsquo;ve started to play with it. And with <a href="https://github.com/pharo-rdbms/Pharo-SQLite3">SQLite</a>. The idea was to evaluate them as possible next backends because there are use cases in which they can be a good fit.</p>
<p>In general, Mapless value proposition reveals itself as an interesting support for when you mostly want to self-host your data without being locked to a vendor.</p>
<p>For example, assuming your code was not entangled with backend&rsquo;s custom features and if you keep the devops and operations issues aside, your plan for migrating from MongoDB to PostgreSQL and vice versa, could be:</p>
<ul>
<li>Instantiating the new repositories used by your app.</li>
<li>Provide a way for your Mapless to have its properties properly indexed for query optimization.</li>
<li>Saving the data without further transformations.</li>
<li>Rewriting your app&rsquo;s queries.</li>
</ul>
<p>That&rsquo;s pretty much it.</p>
<p>With Mapless strategic value your app stays easy to work with and resilient and more adaptable to future changes.</p>
<p>Example, lets assume we add Firebase and DynamoDB support for Maplees. You decide to implement your app on one of these and use them while convenient, but later you migrate from it because the platform policies and economic value turned out to misalign with your business. With Mapless, your capacity for executing such decision would not be as dramatic as it would for other tech-stacks.</p>
<p>For more about the project&rsquo;s direction, you can take a look at the <a href="https://github.com/sebastianconcept/Mapless/issues">current</a> and <a href="https://github.com/sebastianconcept/Mapless/issues?q=is%3Aissue+is%3Aclosed">resolved</a> issues in GitHub. Also, its <a href="https://github.com/sebastianconcept/Mapless/blob/develop/changelog.md">changelog</a> is also fairly well maintained as I&rsquo;ve tried not to miss anything important there.</p>
<p>And I&rsquo;ve started this <a href="https://www.reddit.com/r/mapless_data/">Reddit community</a> to have a forum for the project&rsquo;s questions and as an effort to ease adoption.</p>
<p>You might also find me online in <a href="https://discord.com/invite/QewZMZa">Pharo&rsquo;s Discord server</a> where there is a <code>#database</code> channel for all Pharo persistence related conversations and I&rsquo;ll will help as I can.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Smalltalk IDE I wish would exist</title>
      <link>https://blog.sebastiansastre.co/posts/the-smalltalk-ide-i-wish-would-exist/</link>
      <pubDate>Tue, 02 Jun 2015 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/the-smalltalk-ide-i-wish-would-exist/</guid>
      <description><![CDATA[<p>When I was using Smalltalk in a daily basis I had the chance to understand quite well the things that would make productivity go high.
Here are some sketches I’ve done. I didn’t do the debugger and the package manager system, and both of them have great impact on this but hopefully you can imagine how productive this could be.</p>
<p><img loading="lazy" src="https://miro.medium.com/max/8192/1*ilwBqraykIpuum_qyG9CUw.jpeg"></p>
<p>All collapsed by default.</p>
<p><img loading="lazy" src="https://miro.medium.com/max/8192/1*xpE6dTOv5A1tTn8s2n4FvA.jpeg"></p>
<p>A bottom bar opens with focus on the input ready to evaluate anything or open common tools in one click or key combination.</p>
<p><img loading="lazy" src="https://miro.medium.com/max/8192/1*q78OV588lkpJmwyK24DmVw.jpeg"></p>
<p>The classic time-proven Class Hierarchy Browser has two major changes: 1. it exposes a maximised hierarchy so abstractions can be visually inferred and exploration encouraged and 2. it has a permanently exposed input to a case-insensitive search that will help the user to frictionlessly jump to classes and method names.</p>
<p><img loading="lazy" src="https://miro.medium.com/max/8192/1*oJ5fe8entfCczlhMENZjsg.jpeg"></p>
<p>Everything can be clean again.</p>
<p><img loading="lazy" src="https://miro.medium.com/max/8192/1*dZoK8NFM6-N9cmyYvi5zag.jpeg"></p>
<p>Floating windows with snippets that can spawn from the main expression input at the lower-left. Floating windows are important because they allow drag-grop operations between them.
On the top of the code pane, the Class Hyerarchy Browser should have exposed a couple of buttons (no more than 4 to 6) with commands that should follow a cache strategy. They should be the absolutely most basic and frequently used commands, like remove method, find senders, find implementors, versions and perhaps not anything else (unless we have data that proves is a hot cache hit).</p>
]]></description>
      <content:encoded><![CDATA[<p>When I was using Smalltalk in a daily basis I had the chance to understand quite well the things that would make productivity go high.
Here are some sketches I’ve done. I didn’t do the debugger and the package manager system, and both of them have great impact on this but hopefully you can imagine how productive this could be.</p>
<p><img loading="lazy" src="https://miro.medium.com/max/8192/1*ilwBqraykIpuum_qyG9CUw.jpeg"></p>
<p>All collapsed by default.</p>
<p><img loading="lazy" src="https://miro.medium.com/max/8192/1*xpE6dTOv5A1tTn8s2n4FvA.jpeg"></p>
<p>A bottom bar opens with focus on the input ready to evaluate anything or open common tools in one click or key combination.</p>
<p><img loading="lazy" src="https://miro.medium.com/max/8192/1*q78OV588lkpJmwyK24DmVw.jpeg"></p>
<p>The classic time-proven Class Hierarchy Browser has two major changes: 1. it exposes a maximised hierarchy so abstractions can be visually inferred and exploration encouraged and 2. it has a permanently exposed input to a case-insensitive search that will help the user to frictionlessly jump to classes and method names.</p>
<p><img loading="lazy" src="https://miro.medium.com/max/8192/1*oJ5fe8entfCczlhMENZjsg.jpeg"></p>
<p>Everything can be clean again.</p>
<p><img loading="lazy" src="https://miro.medium.com/max/8192/1*dZoK8NFM6-N9cmyYvi5zag.jpeg"></p>
<p>Floating windows with snippets that can spawn from the main expression input at the lower-left. Floating windows are important because they allow drag-grop operations between them.
On the top of the code pane, the Class Hyerarchy Browser should have exposed a couple of buttons (no more than 4 to 6) with commands that should follow a cache strategy. They should be the absolutely most basic and frequently used commands, like remove method, find senders, find implementors, versions and perhaps not anything else (unless we have data that proves is a hot cache hit).</p>
]]></content:encoded>
    </item>
    <item>
      <title>Run brew cleanup and claim some disk space</title>
      <link>https://blog.sebastiansastre.co/posts/run-brew-cleanup-and-claim-some-disk-space/</link>
      <pubDate>Thu, 12 Mar 2015 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/run-brew-cleanup-and-claim-some-disk-space/</guid>
      <description><![CDATA[<p>I was doing a bit of housekeeping in my MacBook Pro so I can use some additional diskspace.</p>
<p>The starting point was to do a scan with <a href="https://www.omnigroup.com/more">OmniDiskSweeper</a>.</p>
<p>OmniDiskSweeper&rsquo;s User Interface allows you to easily navigate the directories that are occupying space the most so you can investigate what in those big directories is actually useful and what can be archived.</p>
<p>Beside the directories that was just fine to delete, my findings allowed me to see which ones was okay to archive to a big external disk. At the end of that scrutiny freeing the Trash freed up ~50GB which was a nice bunch :)</p>
<p>And then I saw Cellar occupying a lot and I&rsquo;ve  found that I had 6 versions of MongoDB. I only use the latest stable and I have no reason to keep the older ones.</p>
<p>So, if you like me, use brew and don&rsquo;t need to keep older versions of the installed applications and libraries, then doing a cleanup in brew is really handy.</p>
<p>For me today was ~5GB handy :)</p>
<p>Run <code>brew cleanup -n</code> to see which packages would be removed, and when you feel ready hit it with a <code>brew cleanup</code></p>
]]></description>
      <content:encoded><![CDATA[<p>I was doing a bit of housekeeping in my MacBook Pro so I can use some additional diskspace.</p>
<p>The starting point was to do a scan with <a href="https://www.omnigroup.com/more">OmniDiskSweeper</a>.</p>
<p>OmniDiskSweeper&rsquo;s User Interface allows you to easily navigate the directories that are occupying space the most so you can investigate what in those big directories is actually useful and what can be archived.</p>
<p>Beside the directories that was just fine to delete, my findings allowed me to see which ones was okay to archive to a big external disk. At the end of that scrutiny freeing the Trash freed up ~50GB which was a nice bunch :)</p>
<p>And then I saw Cellar occupying a lot and I&rsquo;ve  found that I had 6 versions of MongoDB. I only use the latest stable and I have no reason to keep the older ones.</p>
<p>So, if you like me, use brew and don&rsquo;t need to keep older versions of the installed applications and libraries, then doing a cleanup in brew is really handy.</p>
<p>For me today was ~5GB handy :)</p>
<p>Run <code>brew cleanup -n</code> to see which packages would be removed, and when you feel ready hit it with a <code>brew cleanup</code></p>
]]></content:encoded>
    </item>
    <item>
      <title>Controller expression evaluation on render with Angular</title>
      <link>https://blog.sebastiansastre.co/posts/controller-expression-evaluation-on-render-with-angular/</link>
      <pubDate>Wed, 11 Mar 2015 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/controller-expression-evaluation-on-render-with-angular/</guid>
      <description><![CDATA[<p>This is a really common need that AngularJS is not providing out of the box.</p>
<p>I needed it myself for some projects and you might too?</p>
<p>Ok, even if many will tell you that you should not program in a way that needs to use this feature, I beleive that there is plenty of people that knows exactly what they are doing. There is plenty of cases that have real and justified reasons to use this feature and they can use it properly. In short, when adequate this do not necessarily implies bad design.</p>
<p>So here we go.</p>
<p>What I&rsquo;m using to get expressions evaluated after the render is a custom directive named, you guessed, <code>afterRender</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">define</span><span class="p">([</span><span class="s1">&#39;angular&#39;</span><span class="p">],</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">angular</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;use strict&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">angular</span><span class="p">.</span><span class="nx">module</span><span class="p">(</span><span class="s1">&#39;app.common.after-render&#39;</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">directive</span><span class="p">(</span><span class="s1">&#39;afterRender&#39;</span><span class="p">,</span> <span class="p">[</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">def</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">restrict</span> <span class="o">:</span> <span class="s1">&#39;A&#39;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">        <span class="nx">terminal</span> <span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">transclude</span> <span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">link</span> <span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">scope</span><span class="p">,</span> <span class="nx">element</span><span class="p">,</span> <span class="nx">attrs</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">attrs</span><span class="p">)</span> <span class="p">{</span> <span class="nx">scope</span><span class="p">.</span><span class="nx">$eval</span><span class="p">(</span><span class="nx">attrs</span><span class="p">.</span><span class="nx">afterRender</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="nx">scope</span><span class="p">.</span><span class="nx">$emit</span><span class="p">(</span><span class="s1">&#39;onAfterRender&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">def</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}]);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Transclusion doesn&rsquo;t make sense here, so we set it false and is restricted to &lsquo;A&rsquo; because, to be useful, what we want is to add this into any controller&rsquo;s template by adding it as an attribute like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">after-render</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>which will make the controller owning that piece of template able to observe the event <code>onAfterRender</code> fired by this directive and react with arbitrary code of its own.</p>
<p>Here is an example of such controller&rsquo;s initialize method would look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Makes this controller to have some default initial state and 
</span></span></span><span class="line"><span class="cl">    <span class="c1">// wires the reactions that belong to its concern.
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">$scope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">&#39;onAfterRender&#39;</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(){</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">thatGuyJustRendered</span><span class="p">()});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Reacts doing this and that after that guy had rendered
</span></span></span><span class="line"><span class="cl"><span class="nx">$scope</span><span class="p">.</span><span class="nx">thatGuyJustRendered</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;yep, cool, this computes after that guy had rendered&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ... other controller&#39;s methods ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span><span class="p">();</span>
</span></span></code></pre></div><p>Bonus
There is a bonus. But! this is powerful so it comes with a disclaimenr: this is something not to be abused. I do not endorse bloating the views (templates) with logic because that&rsquo;s something that concerns the controller.</p>
<p>Said that, here is how you&rsquo;d use it to run an arbitrary expression:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">after-render</span><span class="o">=</span><span class="s">&#34;$emit=&#39;onAfterThisConcreteThingRendered&#39;&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>You can put there where you see the $emit any expression you want but you should know that in all my real-world code using the after-render, I only use it to trigger specific events like in that example and program the reaction in the right controller that is wired up with reactions so it knows what to do.</p>
<p>I hope you find it useful and let me know if you need help with it or have any remarks about it</p>
]]></description>
      <content:encoded><![CDATA[<p>This is a really common need that AngularJS is not providing out of the box.</p>
<p>I needed it myself for some projects and you might too?</p>
<p>Ok, even if many will tell you that you should not program in a way that needs to use this feature, I beleive that there is plenty of people that knows exactly what they are doing. There is plenty of cases that have real and justified reasons to use this feature and they can use it properly. In short, when adequate this do not necessarily implies bad design.</p>
<p>So here we go.</p>
<p>What I&rsquo;m using to get expressions evaluated after the render is a custom directive named, you guessed, <code>afterRender</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">define</span><span class="p">([</span><span class="s1">&#39;angular&#39;</span><span class="p">],</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">angular</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;use strict&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">angular</span><span class="p">.</span><span class="nx">module</span><span class="p">(</span><span class="s1">&#39;app.common.after-render&#39;</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">directive</span><span class="p">(</span><span class="s1">&#39;afterRender&#39;</span><span class="p">,</span> <span class="p">[</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">def</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">restrict</span> <span class="o">:</span> <span class="s1">&#39;A&#39;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">        <span class="nx">terminal</span> <span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">transclude</span> <span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">link</span> <span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">scope</span><span class="p">,</span> <span class="nx">element</span><span class="p">,</span> <span class="nx">attrs</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">attrs</span><span class="p">)</span> <span class="p">{</span> <span class="nx">scope</span><span class="p">.</span><span class="nx">$eval</span><span class="p">(</span><span class="nx">attrs</span><span class="p">.</span><span class="nx">afterRender</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="nx">scope</span><span class="p">.</span><span class="nx">$emit</span><span class="p">(</span><span class="s1">&#39;onAfterRender&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">def</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}]);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Transclusion doesn&rsquo;t make sense here, so we set it false and is restricted to &lsquo;A&rsquo; because, to be useful, what we want is to add this into any controller&rsquo;s template by adding it as an attribute like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">after-render</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>which will make the controller owning that piece of template able to observe the event <code>onAfterRender</code> fired by this directive and react with arbitrary code of its own.</p>
<p>Here is an example of such controller&rsquo;s initialize method would look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Makes this controller to have some default initial state and 
</span></span></span><span class="line"><span class="cl">    <span class="c1">// wires the reactions that belong to its concern.
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">$scope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">&#39;onAfterRender&#39;</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(){</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">thatGuyJustRendered</span><span class="p">()});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Reacts doing this and that after that guy had rendered
</span></span></span><span class="line"><span class="cl"><span class="nx">$scope</span><span class="p">.</span><span class="nx">thatGuyJustRendered</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;yep, cool, this computes after that guy had rendered&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ... other controller&#39;s methods ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span><span class="p">();</span>
</span></span></code></pre></div><p>Bonus
There is a bonus. But! this is powerful so it comes with a disclaimenr: this is something not to be abused. I do not endorse bloating the views (templates) with logic because that&rsquo;s something that concerns the controller.</p>
<p>Said that, here is how you&rsquo;d use it to run an arbitrary expression:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">after-render</span><span class="o">=</span><span class="s">&#34;$emit=&#39;onAfterThisConcreteThingRendered&#39;&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>You can put there where you see the $emit any expression you want but you should know that in all my real-world code using the after-render, I only use it to trigger specific events like in that example and program the reaction in the right controller that is wired up with reactions so it knows what to do.</p>
<p>I hope you find it useful and let me know if you need help with it or have any remarks about it</p>
]]></content:encoded>
    </item>
    <item>
      <title>Pharo image shutdown after an OS signal</title>
      <link>https://blog.sebastiansastre.co/posts/pharo-image-shutdown-after-an-os-signal/</link>
      <pubDate>Tue, 10 Feb 2015 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/pharo-image-shutdown-after-an-os-signal/</guid>
      <description><![CDATA[<p>Sometimes you need to control how a worker image starts and stops right from the operative system. You may do it from the terminal but the really important use for this comes with automation and orchestration.</p>
<p>In airflowing, the Pharo worker images start and stop automatically and on command from the OS and in other to shutdown, the images use the technique I&rsquo;m describing here.</p>
<p>To do this you need 3 things:</p>
<ol>
<li>A VM process listening to the TERM signal</li>
<li>A helper object to hold that VM process</li>
<li>A reaction to handle that signal</li>
</ol>
<p>The helper object usually instantiates on image start and holds a VM process running in the lowest priority. On start it will listen for the TERM signal:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Helper</span><span class="nf">&gt;&gt;</span><span class="nv">makeStopHook</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#34;Answers the process that hooks to the 
</span></span></span><span class="line"><span class="cl"><span class="c">    OS signal that makes this worker to shutdown
</span></span></span><span class="line"><span class="cl"><span class="c">    when the VM process receives a TERM signal from
</span></span></span><span class="line"><span class="cl"><span class="c">    the OS.&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="err">^</span> [<span class="o">|</span><span class="nv">semaphore</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="nv">semaphore</span> <span class="o">:=</span> <span class="nc">OSProcess</span> <span class="nf">accessor</span> <span class="nf">forwardSigTerm</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">        <span class="nv">semaphore</span> <span class="nf">wait</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span> <span class="nf">onTerminationSignal</span>] 
</span></span><span class="line"><span class="cl">            <span class="nf">forkAt:</span> <span class="nc">Processor</span> <span class="nf">systemBackgroundPriority</span> 
</span></span><span class="line"><span class="cl">            <span class="nf">named:</span> <span class="s">&#39;Image TERM&#39;</span><span class="p">.</span>
</span></span></code></pre></div><p>Then you need to implement the reaction to handle that OS signal, and since you probably want a clean shutdown without preserving state, you can use:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Helper</span><span class="nf">&gt;&gt;</span><span class="nv">onTerminationSignal</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#34;The process for the VM of this image 
</span></span></span><span class="line"><span class="cl"><span class="c">    has received a TERM signal from the OS.
</span></span></span><span class="line"><span class="cl"><span class="c">    React accordingly&#34;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="nf">self</span> <span class="nf">log:</span> <span class="s">&#39;That&#39;&#39;s all folks. This worker is shutting down. Bye bye...&#39;</span> <span class="nf">level:</span><span class="ss">#messages</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">    <span class="nc">OSProcess</span> <span class="nf">accessor</span> <span class="nf">restoreSigTerm</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">    <span class="nc">SmalltalkImage</span> <span class="nf">current</span> <span class="nf">snapshot:</span> <span class="bp">false</span> <span class="nf">andQuit:</span> <span class="bp">true</span><span class="p">.</span>
</span></span></code></pre></div><p><strong>Bonus</strong>
Here is what you do to stop the VM process on that image so you can do a clean exit on any services it might be providing:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Helper</span><span class="nf">&gt;&gt;</span><span class="nv">stopSignalProcesses</span>
</span></span><span class="line"><span class="cl">    <span class="nf">self</span> <span class="nf">isOnUnixLikeOS</span> <span class="nb">ifFalse:</span>[<span class="o">^</span><span class="bp">nil</span>]<span class="p">.</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span> <span class="nf">log:</span> <span class="s">&#39;Stopping stop hook...&#39;</span> <span class="nf">level:</span><span class="ss">#messages</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">    <span class="nv">stopHook</span> <span class="nf">ifNotNil:</span> [<span class="o">|</span><span class="nv">value</span><span class="nf">|</span>
</span></span><span class="line"><span class="cl">            <span class="nv">value</span> <span class="o">:=</span> <span class="nv">stopHook</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">            <span class="nv">stopHook</span> <span class="o">:=</span> <span class="bp">nil</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">            <span class="nc">OSProcess</span> <span class="nf">accessor</span> <span class="nf">restoreSigTerm</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">            <span class="nv">value</span>]<span class="p">.</span>
</span></span></code></pre></div>]]></description>
      <content:encoded><![CDATA[<p>Sometimes you need to control how a worker image starts and stops right from the operative system. You may do it from the terminal but the really important use for this comes with automation and orchestration.</p>
<p>In airflowing, the Pharo worker images start and stop automatically and on command from the OS and in other to shutdown, the images use the technique I&rsquo;m describing here.</p>
<p>To do this you need 3 things:</p>
<ol>
<li>A VM process listening to the TERM signal</li>
<li>A helper object to hold that VM process</li>
<li>A reaction to handle that signal</li>
</ol>
<p>The helper object usually instantiates on image start and holds a VM process running in the lowest priority. On start it will listen for the TERM signal:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Helper</span><span class="nf">&gt;&gt;</span><span class="nv">makeStopHook</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#34;Answers the process that hooks to the 
</span></span></span><span class="line"><span class="cl"><span class="c">    OS signal that makes this worker to shutdown
</span></span></span><span class="line"><span class="cl"><span class="c">    when the VM process receives a TERM signal from
</span></span></span><span class="line"><span class="cl"><span class="c">    the OS.&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="err">^</span> [<span class="o">|</span><span class="nv">semaphore</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="nv">semaphore</span> <span class="o">:=</span> <span class="nc">OSProcess</span> <span class="nf">accessor</span> <span class="nf">forwardSigTerm</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">        <span class="nv">semaphore</span> <span class="nf">wait</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span> <span class="nf">onTerminationSignal</span>] 
</span></span><span class="line"><span class="cl">            <span class="nf">forkAt:</span> <span class="nc">Processor</span> <span class="nf">systemBackgroundPriority</span> 
</span></span><span class="line"><span class="cl">            <span class="nf">named:</span> <span class="s">&#39;Image TERM&#39;</span><span class="p">.</span>
</span></span></code></pre></div><p>Then you need to implement the reaction to handle that OS signal, and since you probably want a clean shutdown without preserving state, you can use:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Helper</span><span class="nf">&gt;&gt;</span><span class="nv">onTerminationSignal</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#34;The process for the VM of this image 
</span></span></span><span class="line"><span class="cl"><span class="c">    has received a TERM signal from the OS.
</span></span></span><span class="line"><span class="cl"><span class="c">    React accordingly&#34;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="nf">self</span> <span class="nf">log:</span> <span class="s">&#39;That&#39;&#39;s all folks. This worker is shutting down. Bye bye...&#39;</span> <span class="nf">level:</span><span class="ss">#messages</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">    <span class="nc">OSProcess</span> <span class="nf">accessor</span> <span class="nf">restoreSigTerm</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">    <span class="nc">SmalltalkImage</span> <span class="nf">current</span> <span class="nf">snapshot:</span> <span class="bp">false</span> <span class="nf">andQuit:</span> <span class="bp">true</span><span class="p">.</span>
</span></span></code></pre></div><p><strong>Bonus</strong>
Here is what you do to stop the VM process on that image so you can do a clean exit on any services it might be providing:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">Helper</span><span class="nf">&gt;&gt;</span><span class="nv">stopSignalProcesses</span>
</span></span><span class="line"><span class="cl">    <span class="nf">self</span> <span class="nf">isOnUnixLikeOS</span> <span class="nb">ifFalse:</span>[<span class="o">^</span><span class="bp">nil</span>]<span class="p">.</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span> <span class="nf">log:</span> <span class="s">&#39;Stopping stop hook...&#39;</span> <span class="nf">level:</span><span class="ss">#messages</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">    <span class="nv">stopHook</span> <span class="nf">ifNotNil:</span> [<span class="o">|</span><span class="nv">value</span><span class="nf">|</span>
</span></span><span class="line"><span class="cl">            <span class="nv">value</span> <span class="o">:=</span> <span class="nv">stopHook</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">            <span class="nv">stopHook</span> <span class="o">:=</span> <span class="bp">nil</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">            <span class="nc">OSProcess</span> <span class="nf">accessor</span> <span class="nf">restoreSigTerm</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">            <span class="nv">value</span>]<span class="p">.</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Improving the Amber Experience</title>
      <link>https://blog.sebastiansastre.co/posts/improving-the-amber-experience/</link>
      <pubDate>Sat, 31 Jan 2015 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/improving-the-amber-experience/</guid>
      <description><![CDATA[<p>There are many things we can do to have an Amber Smalltalk IDE that makes the difference in terms of developing experience and productivity. The best ones take a lot of effort that is currently unfunded.
Maybe we should start a Kickstart campaign and change that. But in the meantime why not do smaller steps in the right direction? We could do small incremental improvements with what we already have.</p>
<p>So for now, let’s take a look at the current IDEs: Helios and Legacy IDE.</p>
<p>Why I don’t like Helios?</p>
<p>First of all, is totally okay to like Helios and I personally liked that Nicolas tried to innovate on the Smalltalk IDE design. I’ve talked with him many times about it and I’ve helped to implement it with some contributions in the inspector and workspace.
Innovation on the Smalltalk IDE it’s not something trivial because the traditional designs are extremely well validated and pass the test of time.</p>
<p>Still, there is room to innovate the Smalltalk IDE and truth is that even I have already some drafts on what a rockstar Smalltalk IDE would be. I’m validating its design with some inner-circle veteran developers before any disclosing or deciding to do anything beyond some UX/UI drafts.</p>
<p>The thing is I’ve tried hard with Helios and it really didn’t work for me. You could say that is a matter of preference on developing taste for stateful User Interfaces, like “are you a vi person or a non-vi person?”</p>
<p>But is not.</p>
<p>With time I’ve got some muscular training on getting the right key combinations and end up doing things apparently fast. It wasn’t. The deal-breaker for me was a pragmatic test on plain old productivity.</p>
<p>Helios takes too much time to pop-up and open and hides some actions behind a wall of sequenced keystrokes while the classic IDE provides an instant embedded open and is more “mouse friendly.”</p>
<p>In the end, Economics decided for me. Yes, Helios could connect to a remote environment but (a) that does not happen today and (b) might never do because the development of that feature, sadly, is not moving forward. The day to day result is that Helios made me overall less productive and slower than using the good old Legacy IDE.</p>
<p>Hence the divorce.</p>
<p>So after moving away from it I’ve started to see new things and try to figure out new possibilities and I’ve come to the personal conclusion that for the goal of having a more productive and overall better User Experience developing with Amber, it would take significantly less effort to make the Legacy IDE way better than Helios way better.</p>
<p>I am the only one perceiving this?</p>
<p>How do you use Amber to code? Here is a poll I’m running to understand this question better.</p>
<p>I hope this doesn’t sound rhetorical because this is a genuine question I have. I can easily just fork and do the incremental adjustments I want to see done on the classic IDE or, I can bother myself in trying to amplify those benefits for the whole community.
And I don’t know what to do yet.</p>
<p>What do you think? Should I try to promote this Legacy IDE improvements and fixes in the official Amber repo or just fork? Please help this spread and leave a comment with your preference so our community can find it and know.</p>
<p>Here is the issue I’ve tentatively opened:
<a href="https://github.com/amber-smalltalk/amber-attic/issues/3">https://github.com/amber-smalltalk/amber-attic/issues/3</a></p>
<p>Stating these problems as a start:</p>
<ul>
<li>Typography issues</li>
<li>Poor contrast issues</li>
<li>Focus issues</li>
<li>Lack of search keyboard shortcut</li>
<li>Tab close area too small</li>
<li>SUnit button alignment</li>
<li>Horizontal arrow keys should change the pane in focus (on Browser and SUnit)</li>
<li>Vertical arrow keys should change the selected list item when focus is in a list pane</li>
<li>Save button disabled on pristine</li>
<li>Class browse shortcut</li>
<li>Icons on the class list</li>
<li>Icons on the package list</li>
<li>Drag and drop to categorise methods</li>
<li>Marking changed packages</li>
<li>Visual feedback on successful commit finished</li>
<li>Visual feedback on failed commit (handle exception and display proper message)</li>
<li>Select and change theme easily and persist the preference on localStorage</li>
<li>Change theme to be dark by default</li>
</ul>
]]></description>
      <content:encoded><![CDATA[<p>There are many things we can do to have an Amber Smalltalk IDE that makes the difference in terms of developing experience and productivity. The best ones take a lot of effort that is currently unfunded.
Maybe we should start a Kickstart campaign and change that. But in the meantime why not do smaller steps in the right direction? We could do small incremental improvements with what we already have.</p>
<p>So for now, let’s take a look at the current IDEs: Helios and Legacy IDE.</p>
<p>Why I don’t like Helios?</p>
<p>First of all, is totally okay to like Helios and I personally liked that Nicolas tried to innovate on the Smalltalk IDE design. I’ve talked with him many times about it and I’ve helped to implement it with some contributions in the inspector and workspace.
Innovation on the Smalltalk IDE it’s not something trivial because the traditional designs are extremely well validated and pass the test of time.</p>
<p>Still, there is room to innovate the Smalltalk IDE and truth is that even I have already some drafts on what a rockstar Smalltalk IDE would be. I’m validating its design with some inner-circle veteran developers before any disclosing or deciding to do anything beyond some UX/UI drafts.</p>
<p>The thing is I’ve tried hard with Helios and it really didn’t work for me. You could say that is a matter of preference on developing taste for stateful User Interfaces, like “are you a vi person or a non-vi person?”</p>
<p>But is not.</p>
<p>With time I’ve got some muscular training on getting the right key combinations and end up doing things apparently fast. It wasn’t. The deal-breaker for me was a pragmatic test on plain old productivity.</p>
<p>Helios takes too much time to pop-up and open and hides some actions behind a wall of sequenced keystrokes while the classic IDE provides an instant embedded open and is more “mouse friendly.”</p>
<p>In the end, Economics decided for me. Yes, Helios could connect to a remote environment but (a) that does not happen today and (b) might never do because the development of that feature, sadly, is not moving forward. The day to day result is that Helios made me overall less productive and slower than using the good old Legacy IDE.</p>
<p>Hence the divorce.</p>
<p>So after moving away from it I’ve started to see new things and try to figure out new possibilities and I’ve come to the personal conclusion that for the goal of having a more productive and overall better User Experience developing with Amber, it would take significantly less effort to make the Legacy IDE way better than Helios way better.</p>
<p>I am the only one perceiving this?</p>
<p>How do you use Amber to code? Here is a poll I’m running to understand this question better.</p>
<p>I hope this doesn’t sound rhetorical because this is a genuine question I have. I can easily just fork and do the incremental adjustments I want to see done on the classic IDE or, I can bother myself in trying to amplify those benefits for the whole community.
And I don’t know what to do yet.</p>
<p>What do you think? Should I try to promote this Legacy IDE improvements and fixes in the official Amber repo or just fork? Please help this spread and leave a comment with your preference so our community can find it and know.</p>
<p>Here is the issue I’ve tentatively opened:
<a href="https://github.com/amber-smalltalk/amber-attic/issues/3">https://github.com/amber-smalltalk/amber-attic/issues/3</a></p>
<p>Stating these problems as a start:</p>
<ul>
<li>Typography issues</li>
<li>Poor contrast issues</li>
<li>Focus issues</li>
<li>Lack of search keyboard shortcut</li>
<li>Tab close area too small</li>
<li>SUnit button alignment</li>
<li>Horizontal arrow keys should change the pane in focus (on Browser and SUnit)</li>
<li>Vertical arrow keys should change the selected list item when focus is in a list pane</li>
<li>Save button disabled on pristine</li>
<li>Class browse shortcut</li>
<li>Icons on the class list</li>
<li>Icons on the package list</li>
<li>Drag and drop to categorise methods</li>
<li>Marking changed packages</li>
<li>Visual feedback on successful commit finished</li>
<li>Visual feedback on failed commit (handle exception and display proper message)</li>
<li>Select and change theme easily and persist the preference on localStorage</li>
<li>Change theme to be dark by default</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Debugging Announcement issues in Amber Smalltalk</title>
      <link>https://blog.sebastiansastre.co/posts/debugging-announcement-issues-in-amber-smalltalk/</link>
      <pubDate>Tue, 27 Jan 2015 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/debugging-announcement-issues-in-amber-smalltalk/</guid>
      <description><![CDATA[<p>Using an Announcer can give you a lot of flexibility for making your Amber components interact loosely coupled.</p>
<p>And while developing, it is normal to rename a class.</p>
<p>Today I was working with a product and I had this announcement class with a typo <code>OrdedProductEditRejected</code> which I&rsquo;ve renamed to <code>OrderedProductEditRejected</code>.</p>
<p>None of the current Amber IDE&rsquo;s have a refactoring tool to help you to rename globally and changing the methods that are using the class, so you have to go old school and rename in every place yourself.</p>
<p>So far so good. Until you forget to rename it in one method and your app uses that.</p>
<p>What happens in the method using the old name, is that you get nil there and the announcer will throw an exception (nil does not understand #name).</p>
<p>Trying to find references of the class doesn&rsquo;t work anymore because it already has the new name. And if you forgot the exact previous name, the search for references will not help.</p>
<p>So what do you do!?</p>
<p>In flow based applications, the App class implements on:do: in this way:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">App</span><span class="nf">&gt;&gt;on:</span> <span class="nv">ann</span> <span class="nf">do:</span> <span class="nv">aBlock</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="bp">self</span> <span class="nf">announcer</span> <span class="nf">on:</span> <span class="nv">ann</span> <span class="nf">do:</span> <span class="nv">aBlock</span>
</span></span></code></pre></div><p>So every time your code subscribes to observe an event it will send that message. Knowing that, you can debug this kind of problem by setting a halt so when the announcement class is nil it stops and you know what to fix.</p>
<p>This is how it looks:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">App</span><span class="nf">&gt;&gt;on:</span> <span class="nv">ann</span> <span class="nf">do:</span> <span class="nv">aBlock</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nv">ann</span> <span class="nf">ifNil:</span> [ <span class="bp">self</span> <span class="nf">halt</span> ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="bp">self</span> <span class="nf">announcer</span> <span class="nf">on:</span> <span class="nv">ann</span> <span class="nf">do:</span> <span class="nv">aBlock</span>
</span></span></code></pre></div><p>With that, you use your application and when it halts, you&rsquo;ll see in the debugger&rsquo;s stack the sender you forgot to correct.</p>
]]></description>
      <content:encoded><![CDATA[<p>Using an Announcer can give you a lot of flexibility for making your Amber components interact loosely coupled.</p>
<p>And while developing, it is normal to rename a class.</p>
<p>Today I was working with a product and I had this announcement class with a typo <code>OrdedProductEditRejected</code> which I&rsquo;ve renamed to <code>OrderedProductEditRejected</code>.</p>
<p>None of the current Amber IDE&rsquo;s have a refactoring tool to help you to rename globally and changing the methods that are using the class, so you have to go old school and rename in every place yourself.</p>
<p>So far so good. Until you forget to rename it in one method and your app uses that.</p>
<p>What happens in the method using the old name, is that you get nil there and the announcer will throw an exception (nil does not understand #name).</p>
<p>Trying to find references of the class doesn&rsquo;t work anymore because it already has the new name. And if you forgot the exact previous name, the search for references will not help.</p>
<p>So what do you do!?</p>
<p>In flow based applications, the App class implements on:do: in this way:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">App</span><span class="nf">&gt;&gt;on:</span> <span class="nv">ann</span> <span class="nf">do:</span> <span class="nv">aBlock</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="bp">self</span> <span class="nf">announcer</span> <span class="nf">on:</span> <span class="nv">ann</span> <span class="nf">do:</span> <span class="nv">aBlock</span>
</span></span></code></pre></div><p>So every time your code subscribes to observe an event it will send that message. Knowing that, you can debug this kind of problem by setting a halt so when the announcement class is nil it stops and you know what to fix.</p>
<p>This is how it looks:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="line"><span class="cl"><span class="nc">App</span><span class="nf">&gt;&gt;on:</span> <span class="nv">ann</span> <span class="nf">do:</span> <span class="nv">aBlock</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nv">ann</span> <span class="nf">ifNil:</span> [ <span class="bp">self</span> <span class="nf">halt</span> ]<span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="bp">self</span> <span class="nf">announcer</span> <span class="nf">on:</span> <span class="nv">ann</span> <span class="nf">do:</span> <span class="nv">aBlock</span>
</span></span></code></pre></div><p>With that, you use your application and when it halts, you&rsquo;ll see in the debugger&rsquo;s stack the sender you forgot to correct.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Accepting vs. Selective</title>
      <link>https://blog.sebastiansastre.co/posts/accepting-vs-selective/</link>
      <pubDate>Fri, 23 Jan 2015 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/accepting-vs-selective/</guid>
      <description><![CDATA[<p>Keeping the engines running, understandably, demands attention and energy into maintaining the statu quo. A defence of the current state.</p>
<p>Things that go under the hood, the engines that keeps things moving reliably and performing well, they usually demand tighter error margins in all its parts. The people that takes care of the engine need to be selective and rigorous to keep the system producing value.</p>
<p>Backends’ internal parts comes to mind.</p>
<p>Think DevOps. Think about DBA&rsquo;s and sysadmins and all the backend’s gatekeepers curating its reliability. In a strike of rigour, they will happily pospone progress and innovation if things doesn&rsquo;t fit. The profile here is being conservative to only allow what preserves the system’s current production of value.</p>
<p>Now let’s see things from the other side.</p>
<p>In order to create better User Experiences you usually need to go in the exact opposite direction.</p>
<p>A more accepting User Interface is a less rigorous and less selective interface. In other words, an interface that can be used by a more diverse audience. One that is usable by many people with many different forms to see and experience the world.</p>
<p>Finding one, can, in theory, happen by accident or in semi-random attempts, but it&rsquo;s more interesting to think in what could happen if you have a bunch of folks intentionally pursuing that goal. What if they do that systematically?</p>
<p>They can raise the chances of getting there just because believing that they can makes them to pay more attention to detect when they have produced something that resonates with a wider audience.</p>
<p>If they find the thing that works for more people, those guys go from naïve experimenters to startup founders to change makers to hight-impact entrepreneurs.</p>
<p>The Lean Startup method is trying to help on that.</p>
<p>Any product they will try to build, will intentionally be closer to the Robustness Principle.</p>
<p>In computing, the robustness principle, or Postel’s Law, is a general design guideline for software:</p>
<p>Be conservative in what you do, be liberal in what you accept from others (often reworded as “Be conservative in what you send, be liberal in what you accept”).</p>
<p>This is really hard to do because it points the self-criticism (rigor) to oneself and intentionally deescalates the rigor and demands on others. This is usually the exact opposite of what the forces of the Ego ask you to do. The Ego will distract you from that because is basically a self-serving endpoint that neutralises network value creation.</p>
<p>And here is the strategic opportunity:</p>
<p>Because is really hard to do, most people is simply not even willing to try so there is way less competition in that direction.</p>
<p>Because being more accepting and generous to others usually connects people (and that raises the value of the network).</p>
<p>Because is easier to distribute a product in a market if you know how to seduce it and generosity is the right start for that goal.</p>
<p>The Separation of Concerns between frontend and backend allows specialists to do their bests in each domain negotiating a sweet spot in this spectrum of tension between being more accepting vs. more selective.</p>
<p>Fullstack developers resolves this as inner-conflicts.</p>
<p>And the way to level up the value for everybody in anything being made, is by constantly exposing the big picture so those specialists don’t get lost in micro-compartmentalized details that might easily diverge from a better future. When the specialists converge into something meaningful they can do something that people can believe in. When they are doing the thing, they can drive their energy inspiration and passion to it, instead of maintaining the statu quo by just doing the job.</p>
]]></description>
      <content:encoded><![CDATA[<p>Keeping the engines running, understandably, demands attention and energy into maintaining the statu quo. A defence of the current state.</p>
<p>Things that go under the hood, the engines that keeps things moving reliably and performing well, they usually demand tighter error margins in all its parts. The people that takes care of the engine need to be selective and rigorous to keep the system producing value.</p>
<p>Backends’ internal parts comes to mind.</p>
<p>Think DevOps. Think about DBA&rsquo;s and sysadmins and all the backend’s gatekeepers curating its reliability. In a strike of rigour, they will happily pospone progress and innovation if things doesn&rsquo;t fit. The profile here is being conservative to only allow what preserves the system’s current production of value.</p>
<p>Now let’s see things from the other side.</p>
<p>In order to create better User Experiences you usually need to go in the exact opposite direction.</p>
<p>A more accepting User Interface is a less rigorous and less selective interface. In other words, an interface that can be used by a more diverse audience. One that is usable by many people with many different forms to see and experience the world.</p>
<p>Finding one, can, in theory, happen by accident or in semi-random attempts, but it&rsquo;s more interesting to think in what could happen if you have a bunch of folks intentionally pursuing that goal. What if they do that systematically?</p>
<p>They can raise the chances of getting there just because believing that they can makes them to pay more attention to detect when they have produced something that resonates with a wider audience.</p>
<p>If they find the thing that works for more people, those guys go from naïve experimenters to startup founders to change makers to hight-impact entrepreneurs.</p>
<p>The Lean Startup method is trying to help on that.</p>
<p>Any product they will try to build, will intentionally be closer to the Robustness Principle.</p>
<p>In computing, the robustness principle, or Postel’s Law, is a general design guideline for software:</p>
<p>Be conservative in what you do, be liberal in what you accept from others (often reworded as “Be conservative in what you send, be liberal in what you accept”).</p>
<p>This is really hard to do because it points the self-criticism (rigor) to oneself and intentionally deescalates the rigor and demands on others. This is usually the exact opposite of what the forces of the Ego ask you to do. The Ego will distract you from that because is basically a self-serving endpoint that neutralises network value creation.</p>
<p>And here is the strategic opportunity:</p>
<p>Because is really hard to do, most people is simply not even willing to try so there is way less competition in that direction.</p>
<p>Because being more accepting and generous to others usually connects people (and that raises the value of the network).</p>
<p>Because is easier to distribute a product in a market if you know how to seduce it and generosity is the right start for that goal.</p>
<p>The Separation of Concerns between frontend and backend allows specialists to do their bests in each domain negotiating a sweet spot in this spectrum of tension between being more accepting vs. more selective.</p>
<p>Fullstack developers resolves this as inner-conflicts.</p>
<p>And the way to level up the value for everybody in anything being made, is by constantly exposing the big picture so those specialists don’t get lost in micro-compartmentalized details that might easily diverge from a better future. When the specialists converge into something meaningful they can do something that people can believe in. When they are doing the thing, they can drive their energy inspiration and passion to it, instead of maintaining the statu quo by just doing the job.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Flow demoed at Smalltalks2014</title>
      <link>https://blog.sebastiansastre.co/posts/flow-demoed-at-smalltalks2014/</link>
      <pubDate>Fri, 28 Nov 2014 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/flow-demoed-at-smalltalks2014/</guid>
      <description><![CDATA[<p><img loading="lazy" src="https://miro.medium.com/max/2560/1*bh9KXrRuT1gNP9B3xcakDA.jpeg">
In November 5, 6 and 7th I was at Smalltalks2014 where I presented a talk about Startups and Smalltalk that mentions flow.</p>
<p>I want to say a thanks to the organizers for having me there to bring this topic that gave me the opportunity to share this information among many Smalltalk enthusiasts, but also because I met new friends and found some old ones.</p>
<p>One common theme I&rsquo;ve found: I had many great and deep conversations with lots of them. There is something fundamental that is interesting about this technology that seems to make people to be really conscious and thoughtful.</p>
<p>Special thanks to Esteban Maringolo for being a great roommate and pal; to Facundo Vozzi for having big laughs and teaching me about new beers; to Boris Shingarov for deep technical talks about Cathedral&rsquo;s organs and going from the Plank Scale to the Cosmos in half second; to Andrés Valloud for being that pre-adolescent friend sharing games in floppy disks in the 90&rsquo;s rediscovered twenty years later presenting the notion of &ldquo;the kid&rsquo;s play is the man&rsquo;s work&rdquo;; to Peter Hatch for amazing deep chats and World Conquer plans and talking about George Carlin!; to Allen and Rebeca Wirsf-Brock for great long chats and being part of this Smalltalk invention and making JavaScript better; to Juan Vuletich for helping to figure out &ldquo;what should be maximized?&rdquo; when using Metcalfe&rsquo;s Law and to James Foster that so kindly showing me a fantastic way to work with Gemstone/S.</p>
<p>Thank you guys!!!</p>
<p>As you can see in the slides of the talk, one of the topics is Single Page Applications and flow is actually quite on topic about that.
At the end of the day, it was the &ldquo;Show your projects&rdquo; section and I demoed flow in about 10 minutes.</p>
<p>It was nicely received and the video of that might actually get published eventually. I&rsquo;ll share it as soon as I have news on that.
One thing that is worth mentioning is that, with Esteban Maringolo, we&rsquo;re actually thinking of making a Mapless adaptor to Postgres and with the fantastic work of Sebastian Hiedbrink we had a pre-alpha of flow in Gemstone/S.</p>
<p>Can you feel the power?</p>
<p>Excited?</p>
<p>We are!</p>
<p>Here are some photos</p>
<p><img loading="lazy" src="https://miro.medium.com/max/5184/1*xZPUpLZ41JVGEF6AQGbHsw.jpeg"></p>
<p>Peter Hatch giving his talk about Virtual Machines, Critical Thinking and Reliability</p>
<p><img loading="lazy" src="https://miro.medium.com/max/6528/1*yi9RlJuRq9ygxoVBaIp2wg.jpeg"></p>
<p>Andrés Valloud and me &ldquo;twenty years later&rdquo;</p>
<p><img loading="lazy" src="https://miro.medium.com/max/6528/1*hgvNxj7QaqyaIZppEce8hw.jpeg"></p>
<p>Thanks to Esteban Maringolo for noticing that FAST has its own Chris Anderson in Hernán Wilkinson from 10Pines. You almost can notice how he is mesmerized by Nicolás Papagna&rsquo;s TED-style talk☺</p>
<p><img loading="lazy" src="https://miro.medium.com/max/6528/1*kES4VocvIvy6uztlt-ZoEw.jpeg"></p>
<p>From left to right: Esteban Maringolo, Jannik Laval, Sebastian Sastre, Adam Jacques, Prof. John Sarkela, Josh Fridstorm, Kurt Kilpela and</p>
]]></description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="https://miro.medium.com/max/2560/1*bh9KXrRuT1gNP9B3xcakDA.jpeg">
In November 5, 6 and 7th I was at Smalltalks2014 where I presented a talk about Startups and Smalltalk that mentions flow.</p>
<p>I want to say a thanks to the organizers for having me there to bring this topic that gave me the opportunity to share this information among many Smalltalk enthusiasts, but also because I met new friends and found some old ones.</p>
<p>One common theme I&rsquo;ve found: I had many great and deep conversations with lots of them. There is something fundamental that is interesting about this technology that seems to make people to be really conscious and thoughtful.</p>
<p>Special thanks to Esteban Maringolo for being a great roommate and pal; to Facundo Vozzi for having big laughs and teaching me about new beers; to Boris Shingarov for deep technical talks about Cathedral&rsquo;s organs and going from the Plank Scale to the Cosmos in half second; to Andrés Valloud for being that pre-adolescent friend sharing games in floppy disks in the 90&rsquo;s rediscovered twenty years later presenting the notion of &ldquo;the kid&rsquo;s play is the man&rsquo;s work&rdquo;; to Peter Hatch for amazing deep chats and World Conquer plans and talking about George Carlin!; to Allen and Rebeca Wirsf-Brock for great long chats and being part of this Smalltalk invention and making JavaScript better; to Juan Vuletich for helping to figure out &ldquo;what should be maximized?&rdquo; when using Metcalfe&rsquo;s Law and to James Foster that so kindly showing me a fantastic way to work with Gemstone/S.</p>
<p>Thank you guys!!!</p>
<p>As you can see in the slides of the talk, one of the topics is Single Page Applications and flow is actually quite on topic about that.
At the end of the day, it was the &ldquo;Show your projects&rdquo; section and I demoed flow in about 10 minutes.</p>
<p>It was nicely received and the video of that might actually get published eventually. I&rsquo;ll share it as soon as I have news on that.
One thing that is worth mentioning is that, with Esteban Maringolo, we&rsquo;re actually thinking of making a Mapless adaptor to Postgres and with the fantastic work of Sebastian Hiedbrink we had a pre-alpha of flow in Gemstone/S.</p>
<p>Can you feel the power?</p>
<p>Excited?</p>
<p>We are!</p>
<p>Here are some photos</p>
<p><img loading="lazy" src="https://miro.medium.com/max/5184/1*xZPUpLZ41JVGEF6AQGbHsw.jpeg"></p>
<p>Peter Hatch giving his talk about Virtual Machines, Critical Thinking and Reliability</p>
<p><img loading="lazy" src="https://miro.medium.com/max/6528/1*yi9RlJuRq9ygxoVBaIp2wg.jpeg"></p>
<p>Andrés Valloud and me &ldquo;twenty years later&rdquo;</p>
<p><img loading="lazy" src="https://miro.medium.com/max/6528/1*hgvNxj7QaqyaIZppEce8hw.jpeg"></p>
<p>Thanks to Esteban Maringolo for noticing that FAST has its own Chris Anderson in Hernán Wilkinson from 10Pines. You almost can notice how he is mesmerized by Nicolás Papagna&rsquo;s TED-style talk☺</p>
<p><img loading="lazy" src="https://miro.medium.com/max/6528/1*kES4VocvIvy6uztlt-ZoEw.jpeg"></p>
<p>From left to right: Esteban Maringolo, Jannik Laval, Sebastian Sastre, Adam Jacques, Prof. John Sarkela, Josh Fridstorm, Kurt Kilpela and</p>
]]></content:encoded>
    </item>
    <item>
      <title>Controller based title change with Angular</title>
      <link>https://blog.sebastiansastre.co/posts/controller-based-title-change-with-angular/</link>
      <pubDate>Fri, 21 Nov 2014 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/controller-based-title-change-with-angular/</guid>
      <description><![CDATA[<p>Here is a tip on how to keep nice titles in your AngularJS-based SPA - Single Page Application.</p>
<p>The strategy is this:</p>
<ol>
<li>Remember current title (whatever it might be)</li>
<li>Set the title to whatever new value you want</li>
<li>Observe when the controller gets destroyed and</li>
<li>React restoring that previous value in the title</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">controllers</span><span class="p">.</span><span class="nx">controller</span><span class="p">(</span><span class="s1">&#39;AmazingDetailController&#39;</span><span class="p">,</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;$scope&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;$window&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="p">(</span><span class="nx">$scope</span><span class="p">,</span> <span class="nx">$window</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Sets this controller with the expected initial state
</span></span></span><span class="line"><span class="cl">      <span class="c1">// and perform any other initial activity needed
</span></span></span><span class="line"><span class="cl">      <span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1">// Remember the previous title (whatever it might be)
</span></span></span><span class="line"><span class="cl">         <span class="nx">$scope</span><span class="p">.</span><span class="nx">previousTitle</span> <span class="o">=</span> <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="s1">&#39;Amazing Detail!&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1">// Observes $destroy to restore the title of the page to its original
</span></span></span><span class="line"><span class="cl">         <span class="c1">// value once user navigates out of this controller
</span></span></span><span class="line"><span class="cl">         <span class="nx">$scope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">&#39;$destroy&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">           <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">previousTitle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">         <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior 1
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior 2
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior N
</span></span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}]);</span>
</span></span></code></pre></div><p>A realistic use will probably be use a model that is coming from a service from the backend (or cache) or collaborating with other objects somehow. But this strategy is still valid, clean and works like a charm.</p>
<p>Enjoy and let me know how it goes for you!</p>
]]></description>
      <content:encoded><![CDATA[<p>Here is a tip on how to keep nice titles in your AngularJS-based SPA - Single Page Application.</p>
<p>The strategy is this:</p>
<ol>
<li>Remember current title (whatever it might be)</li>
<li>Set the title to whatever new value you want</li>
<li>Observe when the controller gets destroyed and</li>
<li>React restoring that previous value in the title</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">controllers</span><span class="p">.</span><span class="nx">controller</span><span class="p">(</span><span class="s1">&#39;AmazingDetailController&#39;</span><span class="p">,</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;$scope&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;$window&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="p">(</span><span class="nx">$scope</span><span class="p">,</span> <span class="nx">$window</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Sets this controller with the expected initial state
</span></span></span><span class="line"><span class="cl">      <span class="c1">// and perform any other initial activity needed
</span></span></span><span class="line"><span class="cl">      <span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1">// Remember the previous title (whatever it might be)
</span></span></span><span class="line"><span class="cl">         <span class="nx">$scope</span><span class="p">.</span><span class="nx">previousTitle</span> <span class="o">=</span> <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="s1">&#39;Amazing Detail!&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1">// Observes $destroy to restore the title of the page to its original
</span></span></span><span class="line"><span class="cl">         <span class="c1">// value once user navigates out of this controller
</span></span></span><span class="line"><span class="cl">         <span class="nx">$scope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">&#39;$destroy&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">           <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">previousTitle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">         <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior 1
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior 2
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior N
</span></span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}]);</span>
</span></span></code></pre></div><p>A realistic use will probably be use a model that is coming from a service from the backend (or cache) or collaborating with other objects somehow. But this strategy is still valid, clean and works like a charm.</p>
<p>Enjoy and let me know how it goes for you!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Controller-Based Title Change in Angular</title>
      <link>https://blog.sebastiansastre.co/posts/controller-based-title-change-in-angular/</link>
      <pubDate>Fri, 21 Nov 2014 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/controller-based-title-change-in-angular/</guid>
      <description><![CDATA[<p>Here is a tip on how to keep nice titles in your <a href="https://en.wikipedia.org/wiki/AngularJS">AngularJS</a>-based <a href="https://en.wikipedia.org/wiki/Single-page_application">SPA - Single Page Application</a>.</p>
<p>The strategy is this:</p>
<p>Remember current title (whatever it might be)
Set the title to whatever new value you want
Observe when the controller gets destroyed and
React restoring that previous value in the title</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">controllers</span><span class="p">.</span><span class="nx">controller</span><span class="p">(</span><span class="s1">&#39;AmazingDetailController&#39;</span><span class="p">,</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;$scope&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;$window&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="p">(</span><span class="nx">$scope</span><span class="p">,</span> <span class="nx">$window</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Sets this controller with the expected initial state
</span></span></span><span class="line"><span class="cl">      <span class="c1">// and perform any other initial activity needed
</span></span></span><span class="line"><span class="cl">      <span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1">// Remember the previous title (whatever it might be)
</span></span></span><span class="line"><span class="cl">         <span class="nx">$scope</span><span class="p">.</span><span class="nx">previousTitle</span> <span class="o">=</span> <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="s1">&#39;Amazing Detail!&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1">// Observes $destroy to restore the title of the page to its original
</span></span></span><span class="line"><span class="cl">         <span class="c1">// value once user navigates out of this controller
</span></span></span><span class="line"><span class="cl">         <span class="nx">$scope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">&#39;$destroy&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">           <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">previousTitle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">         <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior 1
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior 2
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior N
</span></span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}]);</span>
</span></span></code></pre></div><p>A realistic use will probably be use a model that is coming from a service from the backend (or cache) or collaborating with other objects somehow. But this strategy is still valid, clean and works like a charm.</p>
<p>Enjoy and let me know how it goes for you!</p>
]]></description>
      <content:encoded><![CDATA[<p>Here is a tip on how to keep nice titles in your <a href="https://en.wikipedia.org/wiki/AngularJS">AngularJS</a>-based <a href="https://en.wikipedia.org/wiki/Single-page_application">SPA - Single Page Application</a>.</p>
<p>The strategy is this:</p>
<p>Remember current title (whatever it might be)
Set the title to whatever new value you want
Observe when the controller gets destroyed and
React restoring that previous value in the title</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">controllers</span><span class="p">.</span><span class="nx">controller</span><span class="p">(</span><span class="s1">&#39;AmazingDetailController&#39;</span><span class="p">,</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;$scope&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;$window&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="p">(</span><span class="nx">$scope</span><span class="p">,</span> <span class="nx">$window</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Sets this controller with the expected initial state
</span></span></span><span class="line"><span class="cl">      <span class="c1">// and perform any other initial activity needed
</span></span></span><span class="line"><span class="cl">      <span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1">// Remember the previous title (whatever it might be)
</span></span></span><span class="line"><span class="cl">         <span class="nx">$scope</span><span class="p">.</span><span class="nx">previousTitle</span> <span class="o">=</span> <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="s1">&#39;Amazing Detail!&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1">// Observes $destroy to restore the title of the page to its original
</span></span></span><span class="line"><span class="cl">         <span class="c1">// value once user navigates out of this controller
</span></span></span><span class="line"><span class="cl">         <span class="nx">$scope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">&#39;$destroy&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">           <span class="nx">$window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">previousTitle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">         <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior 1
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior 2
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Does specific behavior N
</span></span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="nx">$scope</span><span class="p">.</span><span class="nx">initialize</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}]);</span>
</span></span></code></pre></div><p>A realistic use will probably be use a model that is coming from a service from the backend (or cache) or collaborating with other objects somehow. But this strategy is still valid, clean and works like a charm.</p>
<p>Enjoy and let me know how it goes for you!</p>
]]></content:encoded>
    </item>
    <item>
      <title>App, main and other controller accessors</title>
      <link>https://blog.sebastiansastre.co/posts/app-main-and-other-controller-accessors/</link>
      <pubDate>Wed, 12 Nov 2014 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/app-main-and-other-controller-accessors/</guid>
      <description><![CDATA[<p>When I&rsquo;ve started flow, one of the features I wanted for it was scaffolding from the front-end and be able to do things like:</p>
<blockquote>
<p>Flow scaffold model: #User</p>
</blockquote>
<p>or:</p>
<blockquote>
<p>Flow scaffold crudFor: #Task</p>
</blockquote>
<p>and have created the Model and Controller classes and accessors in environment so you can continue developing the app pulling things from there.</p>
<p>There are several reasons to have scaffolding features, the utilitarian and most pragmatic reason is that it helps you to show something useful fast. A cheap positive feedback loop that allows you to scaffold the next basic feature of your app. That&rsquo;s something really aligned with flow&rsquo;s mission regarding to productivity.</p>
<p>A secondary good reason is that scaffolding makes it easier to spread basic good practices. It shows by example to new people how to get new models and controllers in an application that can scale in complexity with elegance and high maintainability.</p>
<p>But I&rsquo;m not implementing scaffolding just yet.</p>
<p>The reason why is not being done yet is because we are still in discovery mode of what are those practices. The idea is to really confirm if they are common, general and frequent enough.</p>
<p>In this article, I&rsquo;m going to share the current best candidates for those good practices. If I&rsquo;d have to implement the scaffolding system today, I&rsquo;d make it build the App and its main controller, the main controller accessors, models and router and here I&rsquo;d show you how. Also we&rsquo;ll cover here what&rsquo;s the suggested convention to name things and why is convenient to do it that way.</p>
<p><strong>App and main</strong>
Since version 0.2.7, a flow-based application has the idea of App and main. A default clone of the flow repo will use the App class and MainController class for a sample tutorial-like application.</p>
<p>The App class is a convenient starting point to access and &ldquo;talk with your app&rdquo; whatever app it is. In flow we asume you&rsquo;ll have to investigate things on your app and its controllers so we are trying to make that easy for you. One way is providing easy navigation. Here is how you access the main controller of your application which is the root of the controller&rsquo;s graph.
&ldquo;Used by index.html to start your app.&rdquo;</p>
<blockquote>
<p>App start.</p>
</blockquote>
<blockquote>
<p>&ldquo;Deals with routes definition and is part of the starting process of your frontend.&rdquo;
App setupRouter.</p>
<p>&ldquo;Accessor to the instance of MainController of your app.&rdquo;
App main.</p>
</blockquote>
<p>Take the last one for example, it&rsquo;s 2 words, one of 3 characters the other has 4. Really friendly to type. Amber doesn&rsquo;t have autocomplete so by choosing two short words, we are lowering the friction to access the root controller of your application so you can easily inspect, send commands and see how it reacts. Is safe to say it will be used a lot so you&rsquo;ll profit a lot. For example, an inspect on that code allows you to navigate to any controller that got currently instantiated. That makes easy diagnose possible issues and prototype live-coding the next feature.</p>
<p>The MainController should have as minimum two methods: #home and #reset. You should be able to do:</p>
<blockquote>
<p>&ldquo;Get to the default state on the main controller.&rdquo;
App main reset.</p>
<p>&ldquo;Answers the &lsquo;home&rsquo; controller of the app.&rdquo;
App main home.</p>
<p>Here is code that implements the #reset method taken from a real application:
MainController&raquo;reset</p>
</blockquote>
<p>self navbar deactivateAll.</p>
<blockquote>
<p>self hideAllBut: #navbar.</p>
<p>self home show</p>
</blockquote>
<p>That reset method makes the app to instantly show the home without any blinks on the screen.</p>
<p>Accessors
Now take a look at the #home accessor</p>
<blockquote>
<p>MainController&raquo;home
&ldquo;Answers the home controller of the app.
Creates it lazily if needed.&rdquo;
^ self ifAbsentAt: #home put: [
HomeController
for: model
on: self
appendingTo: ‘#home-wrapper’ asJQuery ]</p>
</blockquote>
<p>So by just naming it with <code>App home</code>, you get it created. Once created, you get that instance as answer every time. That&rsquo;s as close as you can be to zero friction between developer&rsquo;s wish and reality (machine&rsquo;s answer). Just the way things should be.
Note we are using here the class method to instantiate controllers: #for:on:appendingTo:</p>
<blockquote>
<p>Controller class&raquo;for: aModel on: aParentControllerOrNil appendingTo: aHtmlElement
“Answers a new instance of this controller dedicated to aModel,
child of aParentControllerOrNil and meant to be
appended to aHtmlElement.”
^ self new
model: aModel;
parent: aParentControllerOrNil;
parentElement: aHtmlElement;
yourself</p>
</blockquote>
<p>Lets take a look at what it expects:</p>
<ul>
<li>In the for: part it expects the model for the new controller</li>
<li>In the on: part it expects the parent controller, a MainController in this case.</li>
</ul>
<p>Note that the parent controller has sense to be nil only when you instantiate the MainController which is a controller that is the root of the whole graph of controllers.</p>
<p>In the appendingTo: part it expects a DOM element to be the parent element of whatever that new controller needs to render. Typically that would be a <code>&lt;div&gt;</code> with an id named consistently with the controller class name plus the &lsquo;wrapper&rsquo; suffix as you see in that snippet.
The tutorial sample application of a default flow clone has 5 subcontrollers covering from the most basic template based controllers to two-way data binding, composition and client-server RESTful interactions. On its MainController you find 5 accessors: example1, example2, etc. up to example5. They all use the same idea of this #home accessor.</p>
<p><strong>Models</strong></p>
<p>Models in flow are really friendly. Creating the class is enough to start using them. They are Mapless objects which means you don&rsquo;t really need to declare in advance what instVars and accessors they need to have in order to be usable by your application.
This makes really easy to work in a prototypical way encouraging discovery and experimentation, two really strategic features for innovation projects and startups.
Any subclass of Model you add is going to be a Mapless. So basically you only care about the model name and add all your model classes. Later you add the methods that makes them to have special behaviour.
As general rule, Mapless don&rsquo;t need setters and getters, they use DNU to get and set automatically whatever you tell them to. They will answer nil when you ask for something they don&rsquo;t have.
In the following snippet we create an instance of the Visitor class, we set the language according to the browser and we save it in the backend:</p>
<blockquote>
<p>&ldquo;Create a model of the visitor, set its current language and
saves it in the backend.&rdquo;
| visitor |
visitor := Visitor new.
visitor language: window navigator language.
visitor save.
This feature of DNU and returning nil makes them a bit different if you want to have lazy initializations on some of their properties.
How does it look a lazy initialization in a mapless?
Here is another example taken from real code:
Order&raquo;products
&ldquo;Answers the products in this order.
Creates the empty collection if needed.&rdquo;
super products ifNil: [
self products: OrderedCollection new ].</p>
<p>^ super products</p>
</blockquote>
<p><strong>Routes</strong></p>
<p>Lets dig now on the routing system used in flow applications. It all begins at load time. When the frontend is being loaded by your browser using RequireJS, it comes to a moment where it installs the &lsquo;App&rsquo; package containing the App class and all the other classes and methods of your application.</p>
<p>When Amber installs classes on the package loading process, the classes will receive the initialize message on its class side. If there is none, nothing special will happen but if your class implements it, it will perform those custom actions.</p>
<p>In flow, we use this:</p>
<blockquote>
<p>App class&raquo;initialize
self setupRouter</p>
</blockquote>
<p>So even before he application starts, it already will have the router programmed to react correctly to whatever your app should do with the given initial and following URIs.</p>
<p>Lets see the setupRouter now:</p>
<blockquote>
<p>App class&raquo;setupRouter
&ldquo;Program the application reactions for different URI routes.&rdquo;
Router rlite
&ldquo;Normalization of the home route.&rdquo;
add: ‘’ do: [ :r | Router set: ‘#/home’ ];
add: ‘#’ do: [ :r | Router set: ‘#/home’ ];
add: ‘#/’ do: [ :r | Router set: ‘#/home’ ];
add: ‘/’ do: [ :r | Router set: ‘#/home’ ];
add: ‘home’ do: [ :r | App main reset ];
&ldquo;Reactions related to Contact models&rdquo;
add: ‘contacts’ do: [ :r | App main showContacts ];
add: ‘contacts/:id’ do: [ :r |
Router set: ‘#/contacts/’,r params id,’/view’ ];
add: ‘contacts/new’ do: [ :r | App main showNewContact ];
add: ‘contacts/:id/view’ do: [ :r |
App main showContactId: r params id ];
add: ‘contacts/:id/edit’ do: [ :r |
App main editContactId: r params id ];
add: ‘products’ do: [ :r | App main showProducts ];
add: ‘products/:id’ do: [ :r |
Router set: ‘#/products/’,r params id,’/view’ ];
add: ‘products/:id/view’ do: [ :r |
App main showProductId: r params id ];
add: ‘search/:target’ do: [ :r |
App main showSearchResultsFor: r params target ];
yourself</p>
</blockquote>
<p>At this point you&rsquo;ll see that the code is quite self explanatory which is one of the guiding principles behind flow. In the same way this example uses #showNewContact, #showContactId: and editContactId: you would do different routes for different models on your own application.
Bonus
As a bonus, here is how #editContactId: looks like:</p>
<blockquote>
<p>MainController&raquo;editContactId: anId
self hideAllBut: #navbar.
self contactEditor editId: anId</p>
</blockquote>
<p>The editor accessor that you already can infer:</p>
<blockquote>
<p>MainController&raquo;contactEditor
^ self ifAbsentAt: #contactEditor put: [
ContactEditorController
on: self
appendingTo: ‘#contact-editor-wrapper’ asJQuery ]</p>
</blockquote>
<p>The #editId: on the editor controller taking the model from the backend:</p>
<blockquote>
<p>ContactEditorController&raquo;editId: anId
self showPreloader.
Person
findOne: #{ ‘_id’ -&gt; anId }
then: [ :person |
self editContact: person.
self hidePreloader ]
ifNone: [ ]
onError: [ :res | self showError ]</p>
</blockquote>
<p>And finally:</p>
<blockquote>
<p>ContactEditorController&raquo;editContact: aPerson
self model: aPerson.
self show done: [ &ldquo;specific stuff&rdquo; ]</p>
</blockquote>
<p>The #show method in Controller always return a promise so you can do your own custom stuff when the view gets created asynchronously without complicating your code.
So we don&rsquo;t have yet scaffolding today (January 2015) but I hope this article encourages you to write highly readable code on flow.</p>
<p>Happy coding!</p>
]]></description>
      <content:encoded><![CDATA[<p>When I&rsquo;ve started flow, one of the features I wanted for it was scaffolding from the front-end and be able to do things like:</p>
<blockquote>
<p>Flow scaffold model: #User</p>
</blockquote>
<p>or:</p>
<blockquote>
<p>Flow scaffold crudFor: #Task</p>
</blockquote>
<p>and have created the Model and Controller classes and accessors in environment so you can continue developing the app pulling things from there.</p>
<p>There are several reasons to have scaffolding features, the utilitarian and most pragmatic reason is that it helps you to show something useful fast. A cheap positive feedback loop that allows you to scaffold the next basic feature of your app. That&rsquo;s something really aligned with flow&rsquo;s mission regarding to productivity.</p>
<p>A secondary good reason is that scaffolding makes it easier to spread basic good practices. It shows by example to new people how to get new models and controllers in an application that can scale in complexity with elegance and high maintainability.</p>
<p>But I&rsquo;m not implementing scaffolding just yet.</p>
<p>The reason why is not being done yet is because we are still in discovery mode of what are those practices. The idea is to really confirm if they are common, general and frequent enough.</p>
<p>In this article, I&rsquo;m going to share the current best candidates for those good practices. If I&rsquo;d have to implement the scaffolding system today, I&rsquo;d make it build the App and its main controller, the main controller accessors, models and router and here I&rsquo;d show you how. Also we&rsquo;ll cover here what&rsquo;s the suggested convention to name things and why is convenient to do it that way.</p>
<p><strong>App and main</strong>
Since version 0.2.7, a flow-based application has the idea of App and main. A default clone of the flow repo will use the App class and MainController class for a sample tutorial-like application.</p>
<p>The App class is a convenient starting point to access and &ldquo;talk with your app&rdquo; whatever app it is. In flow we asume you&rsquo;ll have to investigate things on your app and its controllers so we are trying to make that easy for you. One way is providing easy navigation. Here is how you access the main controller of your application which is the root of the controller&rsquo;s graph.
&ldquo;Used by index.html to start your app.&rdquo;</p>
<blockquote>
<p>App start.</p>
</blockquote>
<blockquote>
<p>&ldquo;Deals with routes definition and is part of the starting process of your frontend.&rdquo;
App setupRouter.</p>
<p>&ldquo;Accessor to the instance of MainController of your app.&rdquo;
App main.</p>
</blockquote>
<p>Take the last one for example, it&rsquo;s 2 words, one of 3 characters the other has 4. Really friendly to type. Amber doesn&rsquo;t have autocomplete so by choosing two short words, we are lowering the friction to access the root controller of your application so you can easily inspect, send commands and see how it reacts. Is safe to say it will be used a lot so you&rsquo;ll profit a lot. For example, an inspect on that code allows you to navigate to any controller that got currently instantiated. That makes easy diagnose possible issues and prototype live-coding the next feature.</p>
<p>The MainController should have as minimum two methods: #home and #reset. You should be able to do:</p>
<blockquote>
<p>&ldquo;Get to the default state on the main controller.&rdquo;
App main reset.</p>
<p>&ldquo;Answers the &lsquo;home&rsquo; controller of the app.&rdquo;
App main home.</p>
<p>Here is code that implements the #reset method taken from a real application:
MainController&raquo;reset</p>
</blockquote>
<p>self navbar deactivateAll.</p>
<blockquote>
<p>self hideAllBut: #navbar.</p>
<p>self home show</p>
</blockquote>
<p>That reset method makes the app to instantly show the home without any blinks on the screen.</p>
<p>Accessors
Now take a look at the #home accessor</p>
<blockquote>
<p>MainController&raquo;home
&ldquo;Answers the home controller of the app.
Creates it lazily if needed.&rdquo;
^ self ifAbsentAt: #home put: [
HomeController
for: model
on: self
appendingTo: ‘#home-wrapper’ asJQuery ]</p>
</blockquote>
<p>So by just naming it with <code>App home</code>, you get it created. Once created, you get that instance as answer every time. That&rsquo;s as close as you can be to zero friction between developer&rsquo;s wish and reality (machine&rsquo;s answer). Just the way things should be.
Note we are using here the class method to instantiate controllers: #for:on:appendingTo:</p>
<blockquote>
<p>Controller class&raquo;for: aModel on: aParentControllerOrNil appendingTo: aHtmlElement
“Answers a new instance of this controller dedicated to aModel,
child of aParentControllerOrNil and meant to be
appended to aHtmlElement.”
^ self new
model: aModel;
parent: aParentControllerOrNil;
parentElement: aHtmlElement;
yourself</p>
</blockquote>
<p>Lets take a look at what it expects:</p>
<ul>
<li>In the for: part it expects the model for the new controller</li>
<li>In the on: part it expects the parent controller, a MainController in this case.</li>
</ul>
<p>Note that the parent controller has sense to be nil only when you instantiate the MainController which is a controller that is the root of the whole graph of controllers.</p>
<p>In the appendingTo: part it expects a DOM element to be the parent element of whatever that new controller needs to render. Typically that would be a <code>&lt;div&gt;</code> with an id named consistently with the controller class name plus the &lsquo;wrapper&rsquo; suffix as you see in that snippet.
The tutorial sample application of a default flow clone has 5 subcontrollers covering from the most basic template based controllers to two-way data binding, composition and client-server RESTful interactions. On its MainController you find 5 accessors: example1, example2, etc. up to example5. They all use the same idea of this #home accessor.</p>
<p><strong>Models</strong></p>
<p>Models in flow are really friendly. Creating the class is enough to start using them. They are Mapless objects which means you don&rsquo;t really need to declare in advance what instVars and accessors they need to have in order to be usable by your application.
This makes really easy to work in a prototypical way encouraging discovery and experimentation, two really strategic features for innovation projects and startups.
Any subclass of Model you add is going to be a Mapless. So basically you only care about the model name and add all your model classes. Later you add the methods that makes them to have special behaviour.
As general rule, Mapless don&rsquo;t need setters and getters, they use DNU to get and set automatically whatever you tell them to. They will answer nil when you ask for something they don&rsquo;t have.
In the following snippet we create an instance of the Visitor class, we set the language according to the browser and we save it in the backend:</p>
<blockquote>
<p>&ldquo;Create a model of the visitor, set its current language and
saves it in the backend.&rdquo;
| visitor |
visitor := Visitor new.
visitor language: window navigator language.
visitor save.
This feature of DNU and returning nil makes them a bit different if you want to have lazy initializations on some of their properties.
How does it look a lazy initialization in a mapless?
Here is another example taken from real code:
Order&raquo;products
&ldquo;Answers the products in this order.
Creates the empty collection if needed.&rdquo;
super products ifNil: [
self products: OrderedCollection new ].</p>
<p>^ super products</p>
</blockquote>
<p><strong>Routes</strong></p>
<p>Lets dig now on the routing system used in flow applications. It all begins at load time. When the frontend is being loaded by your browser using RequireJS, it comes to a moment where it installs the &lsquo;App&rsquo; package containing the App class and all the other classes and methods of your application.</p>
<p>When Amber installs classes on the package loading process, the classes will receive the initialize message on its class side. If there is none, nothing special will happen but if your class implements it, it will perform those custom actions.</p>
<p>In flow, we use this:</p>
<blockquote>
<p>App class&raquo;initialize
self setupRouter</p>
</blockquote>
<p>So even before he application starts, it already will have the router programmed to react correctly to whatever your app should do with the given initial and following URIs.</p>
<p>Lets see the setupRouter now:</p>
<blockquote>
<p>App class&raquo;setupRouter
&ldquo;Program the application reactions for different URI routes.&rdquo;
Router rlite
&ldquo;Normalization of the home route.&rdquo;
add: ‘’ do: [ :r | Router set: ‘#/home’ ];
add: ‘#’ do: [ :r | Router set: ‘#/home’ ];
add: ‘#/’ do: [ :r | Router set: ‘#/home’ ];
add: ‘/’ do: [ :r | Router set: ‘#/home’ ];
add: ‘home’ do: [ :r | App main reset ];
&ldquo;Reactions related to Contact models&rdquo;
add: ‘contacts’ do: [ :r | App main showContacts ];
add: ‘contacts/:id’ do: [ :r |
Router set: ‘#/contacts/’,r params id,’/view’ ];
add: ‘contacts/new’ do: [ :r | App main showNewContact ];
add: ‘contacts/:id/view’ do: [ :r |
App main showContactId: r params id ];
add: ‘contacts/:id/edit’ do: [ :r |
App main editContactId: r params id ];
add: ‘products’ do: [ :r | App main showProducts ];
add: ‘products/:id’ do: [ :r |
Router set: ‘#/products/’,r params id,’/view’ ];
add: ‘products/:id/view’ do: [ :r |
App main showProductId: r params id ];
add: ‘search/:target’ do: [ :r |
App main showSearchResultsFor: r params target ];
yourself</p>
</blockquote>
<p>At this point you&rsquo;ll see that the code is quite self explanatory which is one of the guiding principles behind flow. In the same way this example uses #showNewContact, #showContactId: and editContactId: you would do different routes for different models on your own application.
Bonus
As a bonus, here is how #editContactId: looks like:</p>
<blockquote>
<p>MainController&raquo;editContactId: anId
self hideAllBut: #navbar.
self contactEditor editId: anId</p>
</blockquote>
<p>The editor accessor that you already can infer:</p>
<blockquote>
<p>MainController&raquo;contactEditor
^ self ifAbsentAt: #contactEditor put: [
ContactEditorController
on: self
appendingTo: ‘#contact-editor-wrapper’ asJQuery ]</p>
</blockquote>
<p>The #editId: on the editor controller taking the model from the backend:</p>
<blockquote>
<p>ContactEditorController&raquo;editId: anId
self showPreloader.
Person
findOne: #{ ‘_id’ -&gt; anId }
then: [ :person |
self editContact: person.
self hidePreloader ]
ifNone: [ ]
onError: [ :res | self showError ]</p>
</blockquote>
<p>And finally:</p>
<blockquote>
<p>ContactEditorController&raquo;editContact: aPerson
self model: aPerson.
self show done: [ &ldquo;specific stuff&rdquo; ]</p>
</blockquote>
<p>The #show method in Controller always return a promise so you can do your own custom stuff when the view gets created asynchronously without complicating your code.
So we don&rsquo;t have yet scaffolding today (January 2015) but I hope this article encourages you to write highly readable code on flow.</p>
<p>Happy coding!</p>
]]></content:encoded>
    </item>
    <item>
      <title>flow</title>
      <link>https://blog.sebastiansastre.co/posts/flow/</link>
      <pubDate>Fri, 10 Oct 2014 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/flow/</guid>
      <description><![CDATA[<p>I’m starting this blog here to make a coder-friendly open conversation with contributors and enthusiasts about flow’s design.</p>
<p>This blog will be doing two things in one move:</p>
<ol>
<li>A source of fresh input, so feedback, so inspiration.</li>
<li>An output about progress on this line of work, so relevance of the mission.</li>
</ol>
<p>But lets back up a bit, what is flow after all?</p>
<p>I like to say that flow is a mission with a framework.</p>
<p>Here is <a href="https://github.com/flow-stack/flow">flow</a>’s mission from the project’s <a href="https://github.com/flow-stack/flow/blob/master/README.md">readme</a>:</p>
<blockquote>
<p>flow’s mission is to provide consultants, startups and software houses with a competitive <a href="https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk.html">Smalltalk</a> full-stack framework that allows them to quickly deliver a demo with all the modern html5 features the market expects today (2014). The idea is that they can tactically use this framework to keep momentum up among their prospects and clients and scale things to full successful projects delivered by kickass productive teams or individuals.</p>
</blockquote>
<p>Why <a href="https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk.html/">Smalltalk</a> you might ask? Is a great question that should be experienced more than described, but if I have to tell you only three things about it, they will be around flow the psychological phenomenon embedded into the <a href="https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk.html">principles of design</a> of Smalltalk itself and I particularly want to highlight that they help with:</p>
<p>*Intuition
Instant feedback (discovery)
Personal mastery
*</p>
<p>Does that resonate in you? I encourage you to join this effort!</p>
<p>It’s all open source, MIT License, and the project&rsquo;s Kanban is being organized in this open board you can join here.
By the way, I had the chance to presented flow at CampSmalltalkVI2014, big thanks to Sebastian Heidbrink for the hard work of organizing it and the invite!</p>
<p>There is no video about the talk but here are some slides I’ve made for it:</p>
<p>flow: a living full-stack ‎framework for the web</p>
<p>~900 views in a couple of days, and many shares? That’s a nice signal that we might just be into something here
Beside a nice quantity of views on those slides what I like is the offline reception of the project and how people reacts to it during the few pairing session I had showing it. People is asking for the petshop sample app already!</p>
<p>I have concrete needs of my own with the framework, mostly being pulled by my clients and projects. That&rsquo;s great because is validated needs that will eventually cascade features to the framework. So all that is expected to be a major drive of course.</p>
<p>At the same time I&rsquo;m interested in innovation in general and how talented individuals and teams deal with innovation for their needs so I’m curious about how would you push this mission forward. I see this blog like a good opportunity to ask design questions and talk about it.</p>
<p>In the next posts I&rsquo;ll write about the current technical challenges and hopefully you might have good ideas to share on how to face them best.</p>
<p>I&rsquo;d like to expose things so people can understand why flow is a curated project and how it&rsquo;s being done.</p>
<p>But before digging ourselves into tech stuff too soon, let me ask you…</p>
<p>What do you think about the mission?</p>
<p>How would you use it?</p>
<p>If not, what stops you in using it? What bumps or showstoppers have you found?</p>
<p>Have your clients asked you to do Single Page Applications for them?</p>
]]></description>
      <content:encoded><![CDATA[<p>I’m starting this blog here to make a coder-friendly open conversation with contributors and enthusiasts about flow’s design.</p>
<p>This blog will be doing two things in one move:</p>
<ol>
<li>A source of fresh input, so feedback, so inspiration.</li>
<li>An output about progress on this line of work, so relevance of the mission.</li>
</ol>
<p>But lets back up a bit, what is flow after all?</p>
<p>I like to say that flow is a mission with a framework.</p>
<p>Here is <a href="https://github.com/flow-stack/flow">flow</a>’s mission from the project’s <a href="https://github.com/flow-stack/flow/blob/master/README.md">readme</a>:</p>
<blockquote>
<p>flow’s mission is to provide consultants, startups and software houses with a competitive <a href="https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk.html">Smalltalk</a> full-stack framework that allows them to quickly deliver a demo with all the modern html5 features the market expects today (2014). The idea is that they can tactically use this framework to keep momentum up among their prospects and clients and scale things to full successful projects delivered by kickass productive teams or individuals.</p>
</blockquote>
<p>Why <a href="https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk.html/">Smalltalk</a> you might ask? Is a great question that should be experienced more than described, but if I have to tell you only three things about it, they will be around flow the psychological phenomenon embedded into the <a href="https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk.html">principles of design</a> of Smalltalk itself and I particularly want to highlight that they help with:</p>
<p>*Intuition
Instant feedback (discovery)
Personal mastery
*</p>
<p>Does that resonate in you? I encourage you to join this effort!</p>
<p>It’s all open source, MIT License, and the project&rsquo;s Kanban is being organized in this open board you can join here.
By the way, I had the chance to presented flow at CampSmalltalkVI2014, big thanks to Sebastian Heidbrink for the hard work of organizing it and the invite!</p>
<p>There is no video about the talk but here are some slides I’ve made for it:</p>
<p>flow: a living full-stack ‎framework for the web</p>
<p>~900 views in a couple of days, and many shares? That’s a nice signal that we might just be into something here
Beside a nice quantity of views on those slides what I like is the offline reception of the project and how people reacts to it during the few pairing session I had showing it. People is asking for the petshop sample app already!</p>
<p>I have concrete needs of my own with the framework, mostly being pulled by my clients and projects. That&rsquo;s great because is validated needs that will eventually cascade features to the framework. So all that is expected to be a major drive of course.</p>
<p>At the same time I&rsquo;m interested in innovation in general and how talented individuals and teams deal with innovation for their needs so I’m curious about how would you push this mission forward. I see this blog like a good opportunity to ask design questions and talk about it.</p>
<p>In the next posts I&rsquo;ll write about the current technical challenges and hopefully you might have good ideas to share on how to face them best.</p>
<p>I&rsquo;d like to expose things so people can understand why flow is a curated project and how it&rsquo;s being done.</p>
<p>But before digging ourselves into tech stuff too soon, let me ask you…</p>
<p>What do you think about the mission?</p>
<p>How would you use it?</p>
<p>If not, what stops you in using it? What bumps or showstoppers have you found?</p>
<p>Have your clients asked you to do Single Page Applications for them?</p>
]]></content:encoded>
    </item>
    <item>
      <title>The way you understood it wrong</title>
      <link>https://blog.sebastiansastre.co/posts/the-way-you-understood-it-wrong/</link>
      <pubDate>Thu, 10 Jul 2014 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/the-way-you-understood-it-wrong/</guid>
      <description><![CDATA[<p>When I was doing engineering, I had a very good algebra teacher.* Those were the days full of vectors and subspaces and subspaces of polynomials and matrices and subspaces of matrices of polynomials.</p>
<p>All from 7 a.m to 12 p.m.</p>
<p>In the middle of that math trench he said once something that my head really saved.</p>
<p>The teacher was explaining yet another concept while the sky was still dark in the morning and there was this guy that asked him to clarify a detail.</p>
<p>The question was asked and the teacher clarified.</p>
<p>He was a good teacher so, before moving on, he asked to this student if the idea was now understood.</p>
<p>He wanted this student validation.</p>
<p>The guy said ‘yes’ and right away this student started to describe all sorts of details about how he was reaching to the wrong conclusions by following an alternative reasoning based on the wrong assumptions he had while not having clear what he later asked about.</p>
<p>The teacher stopped him saying something I&rsquo;ve never forgotten:</p>
<p>No, no, wait, wait. Please do not explain me how you understood it wrong. Tell me instead how you&rsquo;re going to remember it right.</p>
<hr>
<p>Update: together with my good friend Pablo Dobrusin we joined efforts and figured out who the teacher was, he was Fernando Acero at FIUBA - Facultad de Ingeniería Universidad de Buenos Aires.</p>
<p>Timeless insight.</p>
<p>Hats off to teachers like that.</p>
]]></description>
      <content:encoded><![CDATA[<p>When I was doing engineering, I had a very good algebra teacher.* Those were the days full of vectors and subspaces and subspaces of polynomials and matrices and subspaces of matrices of polynomials.</p>
<p>All from 7 a.m to 12 p.m.</p>
<p>In the middle of that math trench he said once something that my head really saved.</p>
<p>The teacher was explaining yet another concept while the sky was still dark in the morning and there was this guy that asked him to clarify a detail.</p>
<p>The question was asked and the teacher clarified.</p>
<p>He was a good teacher so, before moving on, he asked to this student if the idea was now understood.</p>
<p>He wanted this student validation.</p>
<p>The guy said ‘yes’ and right away this student started to describe all sorts of details about how he was reaching to the wrong conclusions by following an alternative reasoning based on the wrong assumptions he had while not having clear what he later asked about.</p>
<p>The teacher stopped him saying something I&rsquo;ve never forgotten:</p>
<p>No, no, wait, wait. Please do not explain me how you understood it wrong. Tell me instead how you&rsquo;re going to remember it right.</p>
<hr>
<p>Update: together with my good friend Pablo Dobrusin we joined efforts and figured out who the teacher was, he was Fernando Acero at FIUBA - Facultad de Ingeniería Universidad de Buenos Aires.</p>
<p>Timeless insight.</p>
<p>Hats off to teachers like that.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Context is King</title>
      <link>https://blog.sebastiansastre.co/posts/context-is-king/</link>
      <pubDate>Tue, 07 Jul 2009 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/context-is-king/</guid>
      <description><![CDATA[<p>When you trust your content is when you can forget it. At that point you’ll get that Context is King.</p>
<blockquote>
<p>…to make a Leonardo you need more than his innate ability. You also need Florence in 1450.
Paul Graham.</p>
</blockquote>
<p>Yes I know… we are not da Vinci, we don’t have Florence in 1450 nor we have a Medici patron for our talent.</p>
<p>Florence in 1450 is context. The problem is that without an appropriate context talent is useless. To be fair, not useless but seriously compromised. That’s why talented people want to run away from places that are hostile to their talent and, the opposite of that problem: knowing how to build appropriated contexts will cultivate talent and open you more doors than working on the content. The good part: today is simpler and cheaper to build desirable contexts so wealth creation can find its way to be deployed in the world.</p>
<p><strong>Some examples:</strong></p>
<ul>
<li>A blank sheet of paper, something to write and a pleasant ambiance.</li>
<li>Your laptop + WiFi in an inspiring coffehouse.</li>
<li>To be able to write about high culture in a conversational tone.</li>
<li>A great story for you blog or, way better, book chapter.</li>
<li>When the world gives you the present of a synchronicity or a serendipity.</li>
<li>Your sketching tools and your favorite music in the background.</li>
<li>Finding the layout that works.</li>
<li>Your design when it has your rules and honors the basic principles at the same time.</li>
<li>To fit that image in the rule of thirds. And the right light and shadows!</li>
<li>When you make your model feeling natural and engaged during the shots.</li>
<li>Your camera and accessories in an historical moment.</li>
<li>To be able to capture technically well without being distracted by technical stuff.</li>
<li>When you can make your actors to inspire one to each other when filming.</li>
<li>Your musical instrument and a couple of friends.</li>
<li>To relax before an audition confident on your practices.</li>
<li>The visual finalization details of your site.</li>
<li>The usability of your application.</li>
<li>The ambiance you were able to architect.</li>
<li>A customer has a problem you can help with.</li>
<li>The results created by your movement before asking for donations.</li>
<li>To say “hello” with a sincere smile.</li>
<li>To listen your interlocutor’s problem.</li>
<li>To ask the right question.</li>
<li>When you renew the inspiration and hope of a member of your community.</li>
<li>Your intervention adding momentum to a project.</li>
</ul>
<p>Because context is dynamic in nature, it can be very brief. When you get one that works take advantage of it putting all the momentum in the substance and commit to the truth. If you do that you can be sure you will be closer to some valuable things:</p>
<p>The identity of what you provide will be faithful to what you are.</p>
<p>Your talents will have a better chance to shine over their opposite keeping what you don’t like in you at a safe distance.
When something you don&rsquo;t like comes up, the feedback will be more useful because is the right input on the right problem, which leads to good diagnosis and good reviews and solutions.</p>
<p>Ultimately this is useful because all the contexts you manage to build are the real signature of your consciusness and you can be familiar with it while, unfortunately, most people don’t even know they have one.</p>
<p><em>Originally published in July 7, 2009. Reviewed in November 17, 2017</em></p>
]]></description>
      <content:encoded><![CDATA[<p>When you trust your content is when you can forget it. At that point you’ll get that Context is King.</p>
<blockquote>
<p>…to make a Leonardo you need more than his innate ability. You also need Florence in 1450.
Paul Graham.</p>
</blockquote>
<p>Yes I know… we are not da Vinci, we don’t have Florence in 1450 nor we have a Medici patron for our talent.</p>
<p>Florence in 1450 is context. The problem is that without an appropriate context talent is useless. To be fair, not useless but seriously compromised. That’s why talented people want to run away from places that are hostile to their talent and, the opposite of that problem: knowing how to build appropriated contexts will cultivate talent and open you more doors than working on the content. The good part: today is simpler and cheaper to build desirable contexts so wealth creation can find its way to be deployed in the world.</p>
<p><strong>Some examples:</strong></p>
<ul>
<li>A blank sheet of paper, something to write and a pleasant ambiance.</li>
<li>Your laptop + WiFi in an inspiring coffehouse.</li>
<li>To be able to write about high culture in a conversational tone.</li>
<li>A great story for you blog or, way better, book chapter.</li>
<li>When the world gives you the present of a synchronicity or a serendipity.</li>
<li>Your sketching tools and your favorite music in the background.</li>
<li>Finding the layout that works.</li>
<li>Your design when it has your rules and honors the basic principles at the same time.</li>
<li>To fit that image in the rule of thirds. And the right light and shadows!</li>
<li>When you make your model feeling natural and engaged during the shots.</li>
<li>Your camera and accessories in an historical moment.</li>
<li>To be able to capture technically well without being distracted by technical stuff.</li>
<li>When you can make your actors to inspire one to each other when filming.</li>
<li>Your musical instrument and a couple of friends.</li>
<li>To relax before an audition confident on your practices.</li>
<li>The visual finalization details of your site.</li>
<li>The usability of your application.</li>
<li>The ambiance you were able to architect.</li>
<li>A customer has a problem you can help with.</li>
<li>The results created by your movement before asking for donations.</li>
<li>To say “hello” with a sincere smile.</li>
<li>To listen your interlocutor’s problem.</li>
<li>To ask the right question.</li>
<li>When you renew the inspiration and hope of a member of your community.</li>
<li>Your intervention adding momentum to a project.</li>
</ul>
<p>Because context is dynamic in nature, it can be very brief. When you get one that works take advantage of it putting all the momentum in the substance and commit to the truth. If you do that you can be sure you will be closer to some valuable things:</p>
<p>The identity of what you provide will be faithful to what you are.</p>
<p>Your talents will have a better chance to shine over their opposite keeping what you don’t like in you at a safe distance.
When something you don&rsquo;t like comes up, the feedback will be more useful because is the right input on the right problem, which leads to good diagnosis and good reviews and solutions.</p>
<p>Ultimately this is useful because all the contexts you manage to build are the real signature of your consciusness and you can be familiar with it while, unfortunately, most people don’t even know they have one.</p>
<p><em>Originally published in July 7, 2009. Reviewed in November 17, 2017</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Subscribe</title>
      <link>https://blog.sebastiansastre.co/subscribe/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://blog.sebastiansastre.co/subscribe/</guid>
      <description><![CDATA[<p>This blog uses a neutral protocol called <strong>RSS</strong>. You can follow new posts in a reader app or service instead of checking the site or relying on a social feed.</p>
<h2 id="how-to-use-it">How to use it</h2>
<p>Pick a reader and add the feed URL below. New posts will show up there; you read in the app, not in the browser.</p>
<p><strong>Recommended tools:</strong></p>
<ul>
<li><strong><a href="https://netnewswire.com/">NetNewsWire</a></strong> — Free, open-source reader for Mac and iOS. Simple and fast.</li>
<li><strong><a href="https://feedly.com/">Feedly</a></strong> — Web and mobile. Good if you want one place for many feeds.</li>
</ul>
<p>Many other readers support RSS (Inoreader, NewsBlur, Reeder, etc.); any app that lets you “add feed” or “subscribe with URL” will work.</p>
<h2 id="feed-url">Feed URL</h2>
<p>Copy this address into your reader:</p>
<p><strong><a href="https://blog.sebastiansastre.co/index.xml">https://blog.sebastiansastre.co/index.xml</a></strong></p>
<p>You can bookmark that link or add it to NetNewsWire, Feedly, or any RSS client. No account on this site is required.</p>
]]></description>
      <content:encoded><![CDATA[<p>This blog uses a neutral protocol called <strong>RSS</strong>. You can follow new posts in a reader app or service instead of checking the site or relying on a social feed.</p>
<h2 id="how-to-use-it">How to use it</h2>
<p>Pick a reader and add the feed URL below. New posts will show up there; you read in the app, not in the browser.</p>
<p><strong>Recommended tools:</strong></p>
<ul>
<li><strong><a href="https://netnewswire.com/">NetNewsWire</a></strong> — Free, open-source reader for Mac and iOS. Simple and fast.</li>
<li><strong><a href="https://feedly.com/">Feedly</a></strong> — Web and mobile. Good if you want one place for many feeds.</li>
</ul>
<p>Many other readers support RSS (Inoreader, NewsBlur, Reeder, etc.); any app that lets you “add feed” or “subscribe with URL” will work.</p>
<h2 id="feed-url">Feed URL</h2>
<p>Copy this address into your reader:</p>
<p><strong><a href="https://blog.sebastiansastre.co/index.xml">https://blog.sebastiansastre.co/index.xml</a></strong></p>
<p>You can bookmark that link or add it to NetNewsWire, Feedly, or any RSS client. No account on this site is required.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
