<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://carlosblanco.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://carlosblanco.github.io/" rel="alternate" type="text/html" /><updated>2026-05-15T14:30:56+00:00</updated><id>https://carlosblanco.github.io/feed.xml</id><title type="html">Software Theory</title><subtitle>Software Engineering, Architecture, and Backend Development.</subtitle><author><name>Carlos Blanco</name></author><entry><title type="html">Why Event-Driven Architecture With a Centralized Dead-Letter Queue Makes Distributed Systems Easily Debuggable</title><link href="https://carlosblanco.github.io/architecture/backend/2026/05/15/event-driven-architecture-troubleshooting.html" rel="alternate" type="text/html" title="Why Event-Driven Architecture With a Centralized Dead-Letter Queue Makes Distributed Systems Easily Debuggable" /><published>2026-05-15T00:00:00+00:00</published><updated>2026-05-15T00:00:00+00:00</updated><id>https://carlosblanco.github.io/architecture/backend/2026/05/15/event-driven-architecture-troubleshooting</id><content type="html" xml:base="https://carlosblanco.github.io/architecture/backend/2026/05/15/event-driven-architecture-troubleshooting.html"><![CDATA[<p>Distributed systems earn their bad reputation for debugging mostly because of how they’re designed, not because distribution is inherently opaque. When services call each other synchronously through long chains of HTTP requests, a single failure anywhere produces symptoms everywhere — and tracing the origin means reconstructing a timeline across logs scattered across a dozen services, each with its own format and clock drift. There’s a better structural answer to this, and it’s one that’s become a default for me on backend systems: a Microservices-based Event-Driven Architecture where services are fully decoupled through a central message broker.</p>

<p>The unit of this architecture is the service itself. Each microservice owns a single domain, talks to its own dedicated database, and is the only thing allowed to write to that data. Externally, it has two communication modes: it publishes and subscribes to events through a Pub/Sub broker — Google Pub/Sub, Apache Kafka, and RabbitMQ are the usual suspects — for anything that can tolerate asynchrony, and exposes REST endpoints for the narrow set of queries that need a synchronous, low-latency answer. There’s also a third pattern worth calling out: a service can accept a command over HTTP and, rather than processing it inline, immediately publish the payload to a topic it subscribes to itself. The HTTP call returns quickly, and the actual work happens asynchronously through the normal event pipeline. This keeps the API surface thin and responsive while letting the processing logic live entirely within the event-driven flow.</p>

<p><img src="/assets/images/service.jpg" alt="A single microservice communicating via Pub/Sub broker and HTTP" /></p>

<p>This decoupling is what makes the system actually debuggable. When something goes wrong in a synchronous HTTP chain, the failure is often masked or transformed by the time it surfaces. In an event-driven system, the failure is contained at the subscriber that couldn’t process the message. When a subscriber throws an exception, instead of silently dropping the message or retrying indefinitely, it publishes to a central DLQ topic with two things attached: the original payload and the full stack trace. Both matter. That’s the key insight behind the Centralized Dead Letter Queue. The payload tells you exactly what the service was trying to process; the stack trace tells you exactly where and why it failed. There’s no ambiguity, no reproducing a request from memory.</p>

<p><img src="/assets/images/service_dlq.jpg" alt="DLQ flow: failed messages routed to a central topic, stored and made available for inspection and replay" /></p>

<p>From there the workflow is straightforward. A dedicated consumer service reads from the general DLQ topic, stores the messages in a file bucket or database, and surfaces them in a UI organized by producer service, topic, error message and date. An engineer investigating a failure opens the DLQ UI, finds the failing messages for the services he owns, reads the stack trace, and has everything needed to diagnose the problem — often without touching a log aggregator at all. Once a fix is deployed, they can replay the original message with its exact payload directly from the UI, confirming the fix and recovering any affected state in one step. If the message is simply invalid and not worth reprocessing, they delete it. The whole loop — detection, diagnosis, fix, recovery — is something a single engineer can close without a war room or a support ticket chain. That’s not just a better developer experience. It’s a structural property of the architecture.</p>]]></content><author><name>Carlos Blanco</name></author><category term="architecture" /><category term="backend" /><category term="event-driven" /><category term="microservices" /><category term="distributed-systems" /><category term="observability" /><category term="dlq" /><summary type="html"><![CDATA[Distributed systems earn their bad reputation for debugging mostly because of how they’re designed, not because distribution is inherently opaque. When services call each other synchronously through long chains of HTTP requests, a single failure anywhere produces symptoms everywhere — and tracing the origin means reconstructing a timeline across logs scattered across a dozen services, each with its own format and clock drift. There’s a better structural answer to this, and it’s one that’s become a default for me on backend systems: a Microservices-based Event-Driven Architecture where services are fully decoupled through a central message broker.]]></summary></entry><entry><title type="html">The DLQ Doesn’t Wait: Engineering for Trust Over Deadlines</title><link href="https://carlosblanco.github.io/engineering-culture/backend/2021/06/10/the-dlq-doesnt-wait.html" rel="alternate" type="text/html" title="The DLQ Doesn’t Wait: Engineering for Trust Over Deadlines" /><published>2021-06-10T00:00:00+00:00</published><updated>2021-06-10T00:00:00+00:00</updated><id>https://carlosblanco.github.io/engineering-culture/backend/2021/06/10/the-dlq-doesnt-wait</id><content type="html" xml:base="https://carlosblanco.github.io/engineering-culture/backend/2021/06/10/the-dlq-doesnt-wait.html"><![CDATA[<p>There’s a line of thinking in software engineering that treats bugs as interruptions — things that pull you away from the “real work” of shipping features. At Nubank I found the opposite to be true. When something lands in the dead letter queue, that <em>is</em> the real work. The DLQ is a centralized queue shared across services where messages end up when processing fails. At any given moment it represents real transactions, real customers, real money that didn’t move the way it was supposed to. The culture here is that engineers don’t triage it at the end of the sprint or escalate it up a chain — they jump on it. Before the feature, before the deadline, before the next standup. You open the logs, trace the failure, open a Jira ticket yourself, and start fixing.</p>

<p>What makes this actually work — and not just a nice-sounding value that collapses under pressure — is the infrastructure underneath it. The observability stack at Nubank is genuinely world-class. Distributed tracing, structured logs, stacktraces linked to kafka payloads and the ability to replay failed messages on demand, dashboards that correlate events across services with enough precision that you can usually pinpoint a failure in minutes rather than hours. Pair that with CI/CD pipelines that let you go from a fix to production without a deployment ceremony, and troubleshooting stops feeling like a disruption to your day. It becomes a loop you can close quickly and cleanly. The investment in tooling is what gives engineers the confidence to own incidents without needing to ask for permission or wait for a war room.</p>

<p>The deeper thing this reflects is trust. Nubank trusts engineers to self-organize around problems, create their own tickets, and report back at standup with status rather than waiting to be assigned. That’s a small cultural detail with big implications — it means the person closest to the code is also the person making the call on priority. No handoff, no delay, no context lost in translation. We’d often walk into a standup having already found the issue, opened the ticket, shipped the fix, and confirmed it in production. The DLQ was back to zero before anyone formally asked about it. That’s the kind of reliability culture that’s hard to document in a process guide because it’s not really a process — it’s just what good engineering looks like when you trust the people doing it.</p>]]></content><author><name>Carlos Blanco</name></author><category term="engineering-culture" /><category term="backend" /><category term="nubank" /><category term="reliability" /><category term="culture" /><category term="observability" /><category term="ci-cd" /><summary type="html"><![CDATA[There’s a line of thinking in software engineering that treats bugs as interruptions — things that pull you away from the “real work” of shipping features. At Nubank I found the opposite to be true. When something lands in the dead letter queue, that is the real work. The DLQ is a centralized queue shared across services where messages end up when processing fails. At any given moment it represents real transactions, real customers, real money that didn’t move the way it was supposed to. The culture here is that engineers don’t triage it at the end of the sprint or escalate it up a chain — they jump on it. Before the feature, before the deadline, before the next standup. You open the logs, trace the failure, open a Jira ticket yourself, and start fixing.]]></summary></entry><entry><title type="html">How Clojure Freed Me from the Ceremony</title><link href="https://carlosblanco.github.io/clojure/functional-programming/2020/10/15/functional-programming-clojure-backend-simplicity.html" rel="alternate" type="text/html" title="How Clojure Freed Me from the Ceremony" /><published>2020-10-15T00:00:00+00:00</published><updated>2020-10-15T00:00:00+00:00</updated><id>https://carlosblanco.github.io/clojure/functional-programming/2020/10/15/functional-programming-clojure-backend-simplicity</id><content type="html" xml:base="https://carlosblanco.github.io/clojure/functional-programming/2020/10/15/functional-programming-clojure-backend-simplicity.html"><![CDATA[<p>A year into working at Nubank — where the backend runs almost entirely on Clojure — I’ve had enough time to form a real opinion rather than a first impression. The thing that keeps standing out to me is how much less code I write to accomplish the same things I used to do in C# or Java. Not fewer features, not less correctness — fewer words. When I was watching <a href="https://www.youtube.com/watch?v=aSEQfqNYNAc">Rich Hickey talk about how standard libraries in some OOP languages end up with deeply layered class hierarchies just to represent simple concepts</a>, something clicked. The complexity isn’t always in the problem. A lot of the time it’s in the language’s way of expressing the solution.</p>

<p>In object-oriented languages I’ve worked with, solving a backend problem comes with a ritual. You need an interface so you can test it. You need a class to implement it. You need a constructor, probably a factory, getters and setters for the fields, and somewhere in there a design pattern that wraps the whole thing in just the right level of abstraction so it doesn’t feel hacky. None of this is wrong — it’s the grammar of the language, and experienced engineers use it well. But it is ceremony. A significant chunk of the code you write isn’t the logic of the problem; it’s the scaffolding the language requires to hold the logic. In Clojure, most of that scaffolding disappears. You write a function that takes data and returns data. If the function gets complicated, you break it into smaller ones. If you need polymorphism, multimethods let you dispatch on anything — not just type. If you need to manage stateful components like a database connection or a message queue, Stuart Sierra’s component pattern handles lifecycle cleanly without turning your codebase into an inheritance diagram.</p>

<p>To be fair, none of this means OOP is overengineered or that people who use it are doing it wrong. The boilerplate exists for good reasons — encapsulation, extensibility, the ability to reason about boundaries. Languages like C# and Java have gotten genuinely good at managing complexity at scale. The difference is that in Clojure, the higher-level abstractions are already there by default. Data is immutable. Functions are first-class. The language nudges you toward simplicity rather than requiring discipline to achieve it. What used to take an interface, two implementations, and a factory now takes a map and a function. The core logic — the part that actually solves the problem — is exposed rather than buried.</p>

<p>After a year of this, going back to a class-heavy codebase feels a bit like snowboarding — you spend a good chunk of the morning renting boots, adjusting bindings, layering up, and getting to the top of the mountain before you actually get to ride down. And riding down is great, genuinely worth it. Just a little bit slower before the fun begins. I don’t think Clojure is the right tool for everything, and I still respect what well-crafted OOP looks like. But for backend systems where the work is fundamentally about transforming data — taking something in, doing something to it, producing something out — functional programming with Clojure maps almost perfectly to the shape of the problem. The code ends up looking a lot like the logic, and that’s a relief.</p>]]></content><author><name>Carlos Blanco</name></author><category term="clojure" /><category term="functional-programming" /><category term="clojure" /><category term="functional-programming" /><category term="backend" /><category term="nubank" /><summary type="html"><![CDATA[A year into working at Nubank — where the backend runs almost entirely on Clojure — I’ve had enough time to form a real opinion rather than a first impression. The thing that keeps standing out to me is how much less code I write to accomplish the same things I used to do in C# or Java. Not fewer features, not less correctness — fewer words. When I was watching Rich Hickey talk about how standard libraries in some OOP languages end up with deeply layered class hierarchies just to represent simple concepts, something clicked. The complexity isn’t always in the problem. A lot of the time it’s in the language’s way of expressing the solution.]]></summary></entry><entry><title type="html">Finding Duplicate Addresses Using the Levenshtein Distance Metric in SQL</title><link href="https://carlosblanco.github.io/sql/algorithms/2018/08/30/finding-duplicate-addresses-using-the-levenshtein-distance-metric-in-sql.html" rel="alternate" type="text/html" title="Finding Duplicate Addresses Using the Levenshtein Distance Metric in SQL" /><published>2018-08-30T00:00:00+00:00</published><updated>2018-08-30T00:00:00+00:00</updated><id>https://carlosblanco.github.io/sql/algorithms/2018/08/30/finding-duplicate-addresses-using-the-levenshtein-distance-metric-in-sql</id><content type="html" xml:base="https://carlosblanco.github.io/sql/algorithms/2018/08/30/finding-duplicate-addresses-using-the-levenshtein-distance-metric-in-sql.html"><![CDATA[<p>We’ve all been in the situation where the <code class="language-plaintext highlighter-rouge">LIKE</code> operator in SQL isn’t good enough for the needs of the task at hand. The <code class="language-plaintext highlighter-rouge">LIKE</code> operator lacks the ability to find strings that are similar but not quite the same. This is when the Levenshtein distance algorithm comes in handy.</p>

<h2 id="what-is-the-levenshtein-distance">What is the Levenshtein distance?</h2>

<p>The Levenshtein distance is a metric that measures the difference between two strings — specifically, the minimum number of single-character edits (insertions, deletions, substitutions) required to change one string into the other. For example, the distance between <code class="language-plaintext highlighter-rouge">"books"</code> and <code class="language-plaintext highlighter-rouge">"back"</code> is three:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>books -&gt; baoks  (substitution: "o" → "a")
baoks -&gt; backs  (substitution: "o" → "c")
backs -&gt; back   (deletion of "s")
</code></pre></div></div>

<p>Phil Factor writes about the edit distance and gives an implementation in MS SQL Server’s T-SQL in his post <a href="https://www.red-gate.com/simple-talk/databases/sql-server/t-sql-programming-sql-server/string-comparisons-in-sql-edit-distance-and-the-levenshtein-algorithm/">String Comparisons in SQL: Edit Distance and the Levenshtein algorithm</a>. Other databases ship with a built-in: <code class="language-plaintext highlighter-rouge">LEVENSHTEIN</code> in PostgreSQL, <code class="language-plaintext highlighter-rouge">EDIT_DISTANCE_SIMILARITY</code> in Oracle, and <code class="language-plaintext highlighter-rouge">EDITDIST3</code> in SQLite.</p>

<p>This is very useful when trying to identify that <code class="language-plaintext highlighter-rouge">931 Main St</code> is the “same” as <code class="language-plaintext highlighter-rouge">931 Main Street</code> — a common problem in any system that stores client information. The trick is to convert the raw distance into a ratio based on the length of the longer string, giving you a percentage of similarity. In my experience, a ratio below 50% works well as a threshold.</p>

<h2 id="an-example-in-postgresql">An example in PostgreSQL</h2>

<p>Say we have this table:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">Addresses</span><span class="p">(</span>
  <span class="n">LINE1</span> <span class="nb">text</span><span class="p">,</span>
  <span class="n">LINE2</span> <span class="nb">text</span>
<span class="p">);</span>
</code></pre></div></div>

<p>With sample data where each row holds two versions of the same address — similar enough for a human, but not for <code class="language-plaintext highlighter-rouge">LIKE</code>:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">Addresses</span> <span class="p">(</span><span class="n">LINE1</span><span class="p">,</span> <span class="n">LINE2</span><span class="p">)</span> <span class="k">VALUES</span>
  <span class="p">(</span><span class="s1">'9128 LEEWARD CIR, INDIANAPOLIS, IN'</span><span class="p">,</span>      <span class="s1">'9128 Leeward Circle, Indianapolis, IN'</span><span class="p">),</span>
  <span class="p">(</span><span class="s1">'101 OCEAN LANE DRIVE, KEY BISCAYNE, FL'</span><span class="p">,</span>  <span class="s1">'101 Ocean Lane Drive Unit 1010, Key Biscayne, FL'</span><span class="p">),</span>
  <span class="p">(</span><span class="s1">'9301 EVERGREEN DRIVE, PARMA, OH'</span><span class="p">,</span>          <span class="s1">'9301 Evergreen Dr, Parma, OH'</span><span class="p">),</span>
  <span class="p">(</span><span class="s1">'1817 BERTRAND DR, LAFAYETTE, LA'</span><span class="p">,</span>          <span class="s1">'2924 Polo Ridge Ct, Charlotte, NC'</span><span class="p">),</span>
  <span class="p">(</span><span class="s1">'201 E 87TH ST, NEW YORK, NY'</span><span class="p">,</span>              <span class="s1">'201 E 87th Street 3E, New York, NY'</span><span class="p">),</span>
  <span class="p">(</span><span class="s1">'799 CARRIGAN AVE, OVIEDO, FL'</span><span class="p">,</span>             <span class="s1">'799 Carrigan Avenue, Oviedo, FL'</span><span class="p">),</span>
  <span class="p">(</span><span class="s1">'4014 CADDIE DRIVE, ACWORTH, GA'</span><span class="p">,</span>           <span class="s1">'10617 WEYBRIDGE DR, TAMPA, FL'</span><span class="p">);</span>
</code></pre></div></div>

<p>Now use <code class="language-plaintext highlighter-rouge">LEVENSHTEIN</code> to calculate a similarity ratio:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span>
  <span class="n">Line1</span><span class="p">,</span>
  <span class="n">Line2</span><span class="p">,</span>
  <span class="n">LEVENSHTEIN</span><span class="p">(</span><span class="k">UPPER</span><span class="p">(</span><span class="n">line1</span><span class="p">),</span> <span class="k">UPPER</span><span class="p">(</span><span class="n">line2</span><span class="p">))</span> <span class="k">AS</span> <span class="n">distance</span><span class="p">,</span>
  <span class="n">LEVENSHTEIN</span><span class="p">(</span><span class="k">UPPER</span><span class="p">(</span><span class="n">line1</span><span class="p">),</span> <span class="k">UPPER</span><span class="p">(</span><span class="n">line2</span><span class="p">))::</span><span class="nb">decimal</span>
    <span class="o">/</span> <span class="n">GREATEST</span><span class="p">(</span><span class="k">length</span><span class="p">(</span><span class="n">line1</span><span class="p">),</span> <span class="k">length</span><span class="p">(</span><span class="n">line2</span><span class="p">))</span> <span class="k">AS</span> <span class="n">ratio</span>
<span class="k">FROM</span> <span class="n">Addresses</span><span class="p">;</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>#</th>
      <th>line1</th>
      <th>line2</th>
      <th>distance</th>
      <th>ratio</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>9128 LEEWARD CIR, INDIANAPOLIS, IN</td>
      <td>9128 Leeward Circle, Indianapolis, IN</td>
      <td>3</td>
      <td>0.08…</td>
    </tr>
    <tr>
      <td>2</td>
      <td>101 OCEAN LANE DRIVE, KEY BISCAYNE, FL</td>
      <td>101 Ocean Lane Drive Unit 1010, Key Biscayne, FL</td>
      <td>10</td>
      <td>0.20…</td>
    </tr>
    <tr>
      <td>3</td>
      <td>9301 EVERGREEN DRIVE, PARMA, OH</td>
      <td>9301 Evergreen Dr, Parma, OH</td>
      <td>3</td>
      <td>0.09…</td>
    </tr>
    <tr>
      <td>4</td>
      <td>1817 BERTRAND DR, LAFAYETTE, LA</td>
      <td>2924 Polo Ridge Ct, Charlotte, NC</td>
      <td>23</td>
      <td>0.69…</td>
    </tr>
    <tr>
      <td>5</td>
      <td>201 E 87TH ST, NEW YORK, NY</td>
      <td>201 E 87th Street 3E, New York, NY</td>
      <td>7</td>
      <td>0.20…</td>
    </tr>
    <tr>
      <td>6</td>
      <td>799 CARRIGAN AVE, OVIEDO, FL</td>
      <td>799 Carrigan Avenue, Oviedo, FL</td>
      <td>3</td>
      <td>0.09…</td>
    </tr>
    <tr>
      <td>7</td>
      <td>4014 CADDIE DRIVE, ACWORTH, GA</td>
      <td>10617 WEYBRIDGE DR, TAMPA, FL</td>
      <td>22</td>
      <td>0.73…</td>
    </tr>
  </tbody>
</table>

<p>Rows 4 and 7 are clearly different addresses (ratio &gt; 0.5). Adding a <code class="language-plaintext highlighter-rouge">WHERE</code> clause filters them out:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span>
  <span class="n">Line1</span><span class="p">,</span>
  <span class="n">Line2</span><span class="p">,</span>
  <span class="n">LEVENSHTEIN</span><span class="p">(</span><span class="k">UPPER</span><span class="p">(</span><span class="n">line1</span><span class="p">),</span> <span class="k">UPPER</span><span class="p">(</span><span class="n">line2</span><span class="p">))</span> <span class="k">AS</span> <span class="n">distance</span><span class="p">,</span>
  <span class="n">LEVENSHTEIN</span><span class="p">(</span><span class="k">UPPER</span><span class="p">(</span><span class="n">line1</span><span class="p">),</span> <span class="k">UPPER</span><span class="p">(</span><span class="n">line2</span><span class="p">))::</span><span class="nb">decimal</span>
    <span class="o">/</span> <span class="n">GREATEST</span><span class="p">(</span><span class="k">length</span><span class="p">(</span><span class="n">line1</span><span class="p">),</span> <span class="k">length</span><span class="p">(</span><span class="n">line2</span><span class="p">))</span> <span class="k">AS</span> <span class="n">ratio</span>
<span class="k">FROM</span> <span class="n">Addresses</span>
<span class="k">WHERE</span> <span class="n">LEVENSHTEIN</span><span class="p">(</span><span class="k">UPPER</span><span class="p">(</span><span class="n">line1</span><span class="p">),</span> <span class="k">UPPER</span><span class="p">(</span><span class="n">line2</span><span class="p">))::</span><span class="nb">decimal</span>
        <span class="o">/</span> <span class="n">GREATEST</span><span class="p">(</span><span class="k">length</span><span class="p">(</span><span class="n">line1</span><span class="p">),</span> <span class="k">length</span><span class="p">(</span><span class="n">line2</span><span class="p">))</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>#</th>
      <th>line1</th>
      <th>line2</th>
      <th>distance</th>
      <th>ratio</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>9128 LEEWARD CIR, INDIANAPOLIS, IN</td>
      <td>9128 Leeward Circle, Indianapolis, IN</td>
      <td>3</td>
      <td>0.08…</td>
    </tr>
    <tr>
      <td>2</td>
      <td>101 OCEAN LANE DRIVE, KEY BISCAYNE, FL</td>
      <td>101 Ocean Lane Drive Unit 1010, Key Biscayne, FL</td>
      <td>10</td>
      <td>0.20…</td>
    </tr>
    <tr>
      <td>3</td>
      <td>9301 EVERGREEN DRIVE, PARMA, OH</td>
      <td>9301 Evergreen Dr, Parma, OH</td>
      <td>3</td>
      <td>0.09…</td>
    </tr>
    <tr>
      <td>5</td>
      <td>201 E 87TH ST, NEW YORK, NY</td>
      <td>201 E 87th Street 3E, New York, NY</td>
      <td>7</td>
      <td>0.20…</td>
    </tr>
    <tr>
      <td>6</td>
      <td>799 CARRIGAN AVE, OVIEDO, FL</td>
      <td>799 Carrigan Avenue, Oviedo, FL</td>
      <td>3</td>
      <td>0.09…</td>
    </tr>
  </tbody>
</table>

<p>You can play with this yourself in <a href="http://sqlfiddle.com/">SqlFiddle</a>.</p>]]></content><author><name>Carlos Blanco</name></author><category term="sql" /><category term="algorithms" /><category term="sql" /><category term="postgresql" /><category term="levenshtein" /><category term="string-matching" /><summary type="html"><![CDATA[We’ve all been in the situation where the LIKE operator in SQL isn’t good enough for the needs of the task at hand. The LIKE operator lacks the ability to find strings that are similar but not quite the same. This is when the Levenshtein distance algorithm comes in handy.]]></summary></entry><entry><title type="html">The Stages of Skill Acquisition in Software Engineering</title><link href="https://carlosblanco.github.io/career/software-engineering/2018/04/05/the-stages-of-skill-acquisition-in-software-engineering.html" rel="alternate" type="text/html" title="The Stages of Skill Acquisition in Software Engineering" /><published>2018-04-05T00:00:00+00:00</published><updated>2018-04-05T00:00:00+00:00</updated><id>https://carlosblanco.github.io/career/software-engineering/2018/04/05/the-stages-of-skill-acquisition-in-software-engineering</id><content type="html" xml:base="https://carlosblanco.github.io/career/software-engineering/2018/04/05/the-stages-of-skill-acquisition-in-software-engineering.html"><![CDATA[<p>Throughout my career I’ve noticed many books and courses that promise to teach you everything you need to know about a programming language or software methodology in 24 hours, 7 days, or a month. This idea always struck me as curious, even back in college, and I confirmed it was largely a fallacy once I started programming professionally.</p>

<p>Learning that way in such a short time means going from beginner to master in 24 hours, 7 days, or a month. That’s practically impossible unless you already have deep knowledge in a discipline that heavily overlaps with the one you’re trying to learn.</p>

<p>Through my own experience and with the help of colleagues and mentors, I’ve come to see that there are four broad stages every programmer goes through on the way to advanced software engineering knowledge: <strong>Novice, Beginner, Intermediate, and Advanced</strong>. These apply to the professional world and are based on the <a href="https://en.wikipedia.org/wiki/Dreyfus_model_of_skill_acquisition">Dreyfus model of skill acquisition</a>, originally proposed at UC Berkeley for the U.S. Air Force Office of Research.</p>

<ol>
  <li>
    <p><strong>Novice.</strong> The person has started learning the fundamentals and has a lot of room to grow. They’re picking up core concepts — inheritance, encapsulation, abstraction, polymorphism — mostly in the abstract, without much depth yet. They can’t complete tasks on their own. Using a hunting analogy: they know a lion exists and that it’s dangerous. But the lion would hunt them, not the other way around.</p>
  </li>
  <li>
    <p><strong>Beginner.</strong> They have enough skills to succeed in their role and genuinely understand software engineering concepts. Their knowledge goes deeper into the specific technology they use — things like LINQ, generics, and extension methods in C#. They can solve problems without help. In the analogy: they can hunt lions, but with guides who lead them to the pride and warn them when one charges — and who might step in themselves if the lion is particularly nasty.</p>
  </li>
  <li>
    <p><strong>Intermediate.</strong> They know which technique to use and when. They can rationalize <em>why</em> things work the way they do, and apply concepts across different contexts — understanding not just how, but why certain patterns work sometimes and fail other times. They’ve mastered their tools (IDEs, languages, RDBMS) and can handle ambiguity. They know advanced universal concepts: composition over inheritance, design patterns, asynchronous programming, unit testing. In the analogy: they hunt lions alone, in record time, and can coach beginners. If someone asks for a coat, they already know you have to go hunt something first.</p>
  </li>
  <li>
    <p><strong>Advanced.</strong> They can do this in their sleep — and have probably debugged something in a dream. They have a solid, internalized understanding of programming and take an active role in leading and teaching others. They understand techniques that transcend their niche: AOP, functional programming, software architecture, when and how to apply each. They define the approach, not just follow it. In the analogy: they train other hunters, know to move downwind so the lion doesn’t smell them, and invent new techniques. They’ll also take down the occasional bear, even though lions are their specialty.</p>
  </li>
</ol>

<p>After these four, there’s a fifth stage that very few people pursue — and that most managers don’t actively push for, since it actually lowers raw productivity. This stage is about research and extending known techniques to solve entirely new classes of problems. In the analogy: this person hunts lions, tigers, bears, and even the Yeti. They give weekend talks about having bagged five Yetis on time and under budget, and their closing slide explains why Gantt charts aren’t the right tool for Yeti hunting.</p>

<p>A newly graduated engineer typically lands at Beginner, though it’s not guaranteed — some stay at Novice even after graduating. Research suggests productivity actually dips when moving from Beginner to Intermediate, then rises sharply through Intermediate and Advanced. The jump from Beginner to Intermediate takes roughly 6–18 months; Intermediate to Advanced takes another 18–36 months. Getting there is not automatic — it depends on the projects you work on, your enthusiasm, and the mentors you find along the way.</p>]]></content><author><name>Carlos Blanco</name></author><category term="career" /><category term="software-engineering" /><category term="career" /><category term="skill-acquisition" /><category term="dreyfus-model" /><summary type="html"><![CDATA[Throughout my career I’ve noticed many books and courses that promise to teach you everything you need to know about a programming language or software methodology in 24 hours, 7 days, or a month. This idea always struck me as curious, even back in college, and I confirmed it was largely a fallacy once I started programming professionally.]]></summary></entry><entry><title type="html">Aspect-Oriented Programming (AOP) in .NET Core and C# Using Autofac and DynamicProxy</title><link href="https://carlosblanco.github.io/.net%20core/c%23/2018/01/19/aop-in-dotnet-core.html" rel="alternate" type="text/html" title="Aspect-Oriented Programming (AOP) in .NET Core and C# Using Autofac and DynamicProxy" /><published>2018-01-19T09:00:00+00:00</published><updated>2018-01-19T09:00:00+00:00</updated><id>https://carlosblanco.github.io/.net%20core/c%23/2018/01/19/aop-in-dotnet-core</id><content type="html" xml:base="https://carlosblanco.github.io/.net%20core/c%23/2018/01/19/aop-in-dotnet-core.html"><![CDATA[<p>Implementing Aspect-Oriented Programming (AOP) in .NET Core 2 was an eye-opener. I love the idea of transferring my .NET programming skills across all platforms, and here’s how I applied the techniques I’m used to on Windows directly on a Mac.</p>

<h3 id="a-new-opportunity">A New Opportunity</h3>
<p>Switching jobs to join Nearsoft gave me a fantastic opportunity to explore the latest developments in the .NET ecosystem. As a long-time fan of C# and its evolving features, I hadn’t been working with the absolute latest versions, but I had been reading up and building exercise projects.</p>

<p>At Nearsoft, I began diving into .NET Core 2 to see how I could implement familiar techniques. What immediately stood out to me was Aspect-Oriented Programming (AOP).</p>

<h3 id="what-is-aspect-oriented-programming">What is Aspect-Oriented Programming?</h3>
<p>AOP is a programming paradigm aimed at increasing modularity by separating cross-cutting concerns from your core business logic. Behaviors that your application needs to perform in multiple places—such as logging, transaction handling, or caching—can be added seamlessly without cluttering your actual code.</p>

<p>For the examples below, we will use <strong>Autofac</strong> and <strong>DynamicProxy</strong> from the Castle project.</p>

<p>You will need the .NET Core SDK installed on your system, and optionally, Visual Studio (VS). On a Mac, you can easily install them using Homebrew:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>macbook:aop cblanco<span class="nv">$ </span>brew <span class="nb">install</span> <span class="nt">--cask</span> dotnet-sdk
macbook:aop cblanco<span class="nv">$ </span>brew <span class="nb">install</span> <span class="nt">--cask</span> visual-studio
</code></pre></div></div>

<h3 id="setting-up-a-new-project">Setting Up a New Project</h3>
<p>With .NET installed on your machine, let’s create a new console project. Open up a terminal window and run the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>macbook:dotnet cblanco<span class="nv">$ </span>dotnet new console <span class="nt">-n</span> aop
</code></pre></div></div>

<p>I’ll be using Visual Studio for Mac for this post, so let’s open the solution there. <code class="language-plaintext highlighter-rouge">cd</code> into the project’s folder, run the solution, and check the console output:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>macbook:aop cblanco<span class="nv">$ </span>dotnet run
Hello World!
</code></pre></div></div>

<h3 id="using-autofac-and-dynamicproxy">Using Autofac and DynamicProxy</h3>
<p>So far, so good. Now let’s implement AOP.</p>

<p>First, add the <code class="language-plaintext highlighter-rouge">Autofac.Extras.DynamicProxy</code> NuGet package to your solution. This package automatically brings in <code class="language-plaintext highlighter-rouge">Autofac</code> and <code class="language-plaintext highlighter-rouge">Castle.Core</code> as dependencies.</p>

<p><code class="language-plaintext highlighter-rouge">Autofac.Extras.DynamicProxy</code> enables the interception of method calls on Autofac components. This matches the goal of AOP perfectly.</p>

<p>There are four steps to implementing interception using DynamicProxy:</p>

<ol>
  <li>Create Interceptors.</li>
  <li>Register Interceptors with Autofac.</li>
  <li>Enable Interception on Types.</li>
  <li>Associate Interceptors with Types to be Intercepted.</li>
</ol>

<p>We will implement two interceptors: one for logging and one for caching. Then, we will combine them to see how the whole system works together.</p>

<h3 id="step-1-implementing-a-logger">Step 1: Implementing a Logger</h3>
<p>The first step is to implement the <code class="language-plaintext highlighter-rouge">Castle.DynamicProxy.IInterceptor</code> interface. This interceptor will log which method is being executed, the parameter values it receives, and its total execution time.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Logger</span> <span class="p">:</span> <span class="n">IInterceptor</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">TextWriter</span> <span class="n">_writer</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">Logger</span><span class="p">(</span><span class="n">TextWriter</span> <span class="n">writer</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_writer</span> <span class="p">=</span> <span class="n">writer</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">writer</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">Intercept</span><span class="p">(</span><span class="n">IInvocation</span> <span class="n">invocation</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="s">$"</span><span class="p">{</span><span class="n">invocation</span><span class="p">.</span><span class="n">Method</span><span class="p">.</span><span class="n">DeclaringType</span><span class="p">}</span><span class="s">.</span><span class="p">{</span><span class="n">invocation</span><span class="p">.</span><span class="n">Method</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">"</span><span class="p">;</span>
        <span class="kt">var</span> <span class="n">args</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="s">", "</span><span class="p">,</span> <span class="n">invocation</span><span class="p">.</span><span class="n">Arguments</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">a</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="n">a</span> <span class="p">??</span> <span class="s">""</span><span class="p">).</span><span class="nf">ToString</span><span class="p">()));</span>

        <span class="n">_writer</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"Calling: </span><span class="p">{</span><span class="n">name</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
        <span class="n">_writer</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"Args: </span><span class="p">{</span><span class="n">args</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>

        <span class="kt">var</span> <span class="n">watch</span> <span class="p">=</span> <span class="n">System</span><span class="p">.</span><span class="n">Diagnostics</span><span class="p">.</span><span class="n">Stopwatch</span><span class="p">.</span><span class="nf">StartNew</span><span class="p">();</span>
        
        <span class="c1">// The intercepted method is executed here.</span>
        <span class="n">invocation</span><span class="p">.</span><span class="nf">Proceed</span><span class="p">();</span> 
        
        <span class="n">watch</span><span class="p">.</span><span class="nf">Stop</span><span class="p">();</span>
        <span class="kt">var</span> <span class="n">executionTime</span> <span class="p">=</span> <span class="n">watch</span><span class="p">.</span><span class="n">ElapsedMilliseconds</span><span class="p">;</span>

        <span class="n">_writer</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"Done: result was </span><span class="p">{</span><span class="n">invocation</span><span class="p">.</span><span class="n">ReturnValue</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
        <span class="n">_writer</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"Execution Time: </span><span class="p">{</span><span class="n">executionTime</span><span class="p">}</span><span class="s"> ms."</span><span class="p">);</span>
        <span class="n">_writer</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="step-2-registering-with-autofac">Step 2: Registering with Autofac</h3>
<p>Next, we need to register our interceptor with Autofac in our composition root. To set the <code class="language-plaintext highlighter-rouge">Logger</code> to output to the console, we simply pass <code class="language-plaintext highlighter-rouge">Console.Out</code> as a parameter to its constructor:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">b</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ContainerBuilder</span><span class="p">();</span>
<span class="n">b</span><span class="p">.</span><span class="nf">Register</span><span class="p">(</span><span class="n">i</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="nf">Logger</span><span class="p">(</span><span class="n">Console</span><span class="p">.</span><span class="n">Out</span><span class="p">));</span>
<span class="kt">var</span> <span class="n">container</span> <span class="p">=</span> <span class="n">b</span><span class="p">.</span><span class="nf">Build</span><span class="p">();</span>
</code></pre></div></div>

<h3 id="step-3-enabling-interceptors">Step 3: Enabling Interceptors</h3>
<p>The third step is to enable interceptors on our types by calling the <code class="language-plaintext highlighter-rouge">EnableInterfaceInterceptors()</code> method during registration.</p>

<p>For this example, we’ll use a very simple <code class="language-plaintext highlighter-rouge">Calculator</code> class and an <code class="language-plaintext highlighter-rouge">ICalculator</code> interface as our intercepted type:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">ICalculator</span> 
<span class="p">{</span>
    <span class="kt">int</span> <span class="nf">Add</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">Calculator</span> <span class="p">:</span> <span class="n">ICalculator</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="nf">Add</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">a</span> <span class="p">+</span> <span class="n">b</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Modify the composition root to register this type and enable interceptions:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">b</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ContainerBuilder</span><span class="p">();</span>
<span class="n">b</span><span class="p">.</span><span class="nf">Register</span><span class="p">(</span><span class="n">i</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="nf">Logger</span><span class="p">(</span><span class="n">Console</span><span class="p">.</span><span class="n">Out</span><span class="p">));</span>
<span class="n">b</span><span class="p">.</span><span class="n">RegisterType</span><span class="p">&lt;</span><span class="n">Calculator</span><span class="p">&gt;().</span><span class="n">As</span><span class="p">&lt;</span><span class="n">ICalculator</span><span class="p">&gt;().</span><span class="nf">EnableInterfaceInterceptors</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">container</span> <span class="p">=</span> <span class="n">b</span><span class="p">.</span><span class="nf">Build</span><span class="p">();</span>
</code></pre></div></div>

<h3 id="step-4-associating-interceptors">Step 4: Associating Interceptors</h3>
<p>Finally, associate the interceptors with your types. You can do this right in the registration chain by calling the <code class="language-plaintext highlighter-rouge">InterceptedBy</code> method:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">b</span><span class="p">.</span><span class="n">RegisterType</span><span class="p">&lt;</span><span class="n">Calculator</span><span class="p">&gt;()</span>
 <span class="p">.</span><span class="n">As</span><span class="p">&lt;</span><span class="n">ICalculator</span><span class="p">&gt;()</span>
 <span class="p">.</span><span class="nf">EnableInterfaceInterceptors</span><span class="p">()</span>
 <span class="p">.</span><span class="nf">InterceptedBy</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">Logger</span><span class="p">));</span>
</code></pre></div></div>

<p>Once set up, the methods in the <code class="language-plaintext highlighter-rouge">Calculator</code> type will be intercepted by the <code class="language-plaintext highlighter-rouge">Logger</code>, outputting the following to the console:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>macbook:aop cblanco<span class="nv">$ </span>dotnet run
Calling: aop.Domain.ICalculator.Add
Args: 5, 8
Done: result was 13
Execution Time: 0 ms.
</code></pre></div></div>

<h3 id="gotchas">Gotchas</h3>
<p>It’s worth noting that you can also use attributes to associate interceptors with types. However, there are some caveats—for instance, you cannot use interception with private classes. You can find more details in the Autofac documentation.</p>

<h3 id="taking-it-further-implementing-a-memory-cache">Taking It Further: Implementing a Memory Cache</h3>
<p>We’ve successfully wrapped our business logic with a logging interceptor without intermingling the code. But we can take this further by layering interceptors to stack functionality.</p>

<p>Let’s implement a simple memory cache. While not production-ready, it demonstrates how to combine interceptors to prevent executing an expensive method twice for the same arguments.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">MemoryCaching</span> <span class="p">:</span> <span class="n">IInterceptor</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;</span> <span class="n">_cache</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;();</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">Intercept</span><span class="p">(</span><span class="n">IInvocation</span> <span class="n">invocation</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="s">$"</span><span class="p">{</span><span class="n">invocation</span><span class="p">.</span><span class="n">Method</span><span class="p">.</span><span class="n">DeclaringType</span><span class="p">}</span><span class="s">_</span><span class="p">{</span><span class="n">invocation</span><span class="p">.</span><span class="n">Method</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">"</span><span class="p">;</span>
        <span class="kt">var</span> <span class="n">args</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="s">", "</span><span class="p">,</span> <span class="n">invocation</span><span class="p">.</span><span class="n">Arguments</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">a</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="n">a</span> <span class="p">??</span> <span class="s">""</span><span class="p">).</span><span class="nf">ToString</span><span class="p">()));</span>
        <span class="kt">var</span> <span class="n">cacheKey</span> <span class="p">=</span> <span class="s">$"</span><span class="p">{</span><span class="n">name</span><span class="p">}</span><span class="s">|</span><span class="p">{</span><span class="n">args</span><span class="p">}</span><span class="s">"</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(!</span><span class="n">_cache</span><span class="p">.</span><span class="nf">TryGetValue</span><span class="p">(</span><span class="n">cacheKey</span><span class="p">,</span> <span class="k">out</span> <span class="kt">object</span> <span class="n">returnValue</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="n">invocation</span><span class="p">.</span><span class="nf">Proceed</span><span class="p">();</span>
            <span class="n">returnValue</span> <span class="p">=</span> <span class="n">invocation</span><span class="p">.</span><span class="n">ReturnValue</span><span class="p">;</span>
            <span class="n">_cache</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">cacheKey</span><span class="p">,</span> <span class="n">returnValue</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">else</span>
        <span class="p">{</span>
            <span class="n">invocation</span><span class="p">.</span><span class="n">ReturnValue</span> <span class="p">=</span> <span class="n">returnValue</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><em>(Note: A robust implementation would need to handle the serialization of non-primitive arguments and create a proper string hash for the cache key, perhaps using Json.NET and xxHash.)</em></p>

<h3 id="layering-interceptors">Layering Interceptors</h3>
<p>Register both interceptors in your composition root and chain them onto the <code class="language-plaintext highlighter-rouge">Calculator</code> type:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">b</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ContainerBuilder</span><span class="p">();</span>

<span class="n">b</span><span class="p">.</span><span class="nf">Register</span><span class="p">(</span><span class="n">i</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="nf">Logger</span><span class="p">(</span><span class="n">Console</span><span class="p">.</span><span class="n">Out</span><span class="p">));</span>
<span class="n">b</span><span class="p">.</span><span class="nf">Register</span><span class="p">(</span><span class="n">i</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="nf">MemoryCaching</span><span class="p">());</span>

<span class="n">b</span><span class="p">.</span><span class="n">RegisterType</span><span class="p">&lt;</span><span class="n">Calculator</span><span class="p">&gt;()</span>
 <span class="p">.</span><span class="n">As</span><span class="p">&lt;</span><span class="n">ICalculator</span><span class="p">&gt;()</span>
 <span class="p">.</span><span class="nf">EnableInterfaceInterceptors</span><span class="p">()</span>
 <span class="p">.</span><span class="nf">InterceptedBy</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">Logger</span><span class="p">))</span>
 <span class="p">.</span><span class="nf">InterceptedBy</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">MemoryCaching</span><span class="p">));</span>

<span class="kt">var</span> <span class="n">container</span> <span class="p">=</span> <span class="n">b</span><span class="p">.</span><span class="nf">Build</span><span class="p">();</span>
</code></pre></div></div>

<p>Now, let’s resolve the calculator and execute the <code class="language-plaintext highlighter-rouge">Add</code> method a few times:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">calc</span> <span class="p">=</span> <span class="n">container</span><span class="p">.</span><span class="n">Resolve</span><span class="p">&lt;</span><span class="n">ICalculator</span><span class="p">&gt;();</span>

<span class="n">calc</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="m">5</span><span class="p">,</span> <span class="m">8</span><span class="p">);</span>
<span class="n">calc</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="m">5</span><span class="p">,</span> <span class="m">8</span><span class="p">);</span>
<span class="n">calc</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">8</span><span class="p">);</span>
<span class="n">calc</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">8</span><span class="p">);</span>
<span class="n">calc</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="m">5</span><span class="p">,</span> <span class="m">8</span><span class="p">);</span>
</code></pre></div></div>

<p>(To make the execution time difference obvious, I temporarily added a 1000ms thread sleep inside the Add method). 
As the terminal output shows, the first time a method is executed with specific parameters, it takes time. Subsequent calls with the same values are instant because the cache intercepts the call and returns the stored value:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>macbook:aop cblanco<span class="nv">$ </span>dotnet run

Calling: aop.Domain.ICalculator.Add
Args: 5, 8
Done: result was 13
Execution Time: 1006 ms.

Calling: aop.Domain.ICalculator.Add
Args: 5, 8
Done: result was 13
Execution Time: 0 ms.

Calling: aop.Domain.ICalculator.Add
Args: 6, 8
Done: result was 14
Execution Time: 1004 ms.

Calling: aop.Domain.ICalculator.Add
Args: 6, 8
Done: result was 14
Execution Time: 0 ms.

Calling: aop.Domain.ICalculator.Add
Args: 5, 8
Done: result was 13
Execution Time: 0 ms.
</code></pre></div></div>

<h3 id="final-thoughts">Final Thoughts</h3>

<p>We combined two interceptors to add rich functionality (logging and caching) to our app without modifying a single line of code inside the Calculator domain class. This makes our code easier to extend, maintain, and read.</p>

<p>Writing this post on a Mac using Visual Studio and .NET has been a great experience. I love that Microsoft is porting its technologies to other platforms, allowing us to work with the languages we love without being tied to a single OS.</p>]]></content><author><name>Carlos Blanco</name></author><category term=".NET Core" /><category term="C#" /><category term="AOP" /><category term="Autofac" /><category term="DynamicProxy" /><category term="MacOS" /><category term="Tutorial" /><summary type="html"><![CDATA[Implementing Aspect-Oriented Programming (AOP) in .NET Core 2 was an eye-opener. I love the idea of transferring my .NET programming skills across all platforms, and here’s how I applied the techniques I’m used to on Windows directly on a Mac.]]></summary></entry></feed>