<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
	<channel>
		<title>Markus Tacker · Software Crafter</title>
		<link>https://coderbyheart.com/</link>
		<atom:link href="https://coderbyheart.com/rss.xml" rel="self" type="application/rss+xml" />
		<description>Software Crafter</description>
		<language>en</language>
		<lastBuildDate>Sun, 17 May 2026 13:00:00 GMT</lastBuildDate>
		<item>
			<title>ES+CQRS on AWS: the simplest version yet</title>
			<link>https://coderbyheart.com/es-and-cqrs-on-aws-simplified</link>
			<guid isPermaLink="true">https://coderbyheart.com/es-and-cqrs-on-aws-simplified</guid>
			<pubDate>Sun, 17 May 2026 13:00:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>I have been iterating on Event Sourcing and CQRS architectures for nearly a decade. When I first started with this pattern around 2016 and 2017, the implementation was considerably more complex — a lot of ceremony, a lot of infrastructure, and a lot of things that could go wrong. Over the years I have stripped it back again and again, and what I have arrived at now is something I&apos;m genuinely happy with: a small, focused TypeScript library backed by DynamoDB that does exactly what it needs to and nothing more.</description>
			<content:encoded><![CDATA[
<p>
  <aside class="hero"><picture style="aspect-ratio: 1408 / 768" data-cdn><source srcset="https://7w7z6ydf2htamqdsm6nbxm7sma0nkltc.lambda-url.eu-central-1.on.aws/coderbyheart.com/media/d78095fa7d4f8a085e24ae3dc056c7919ab55644dc4e8a729d869b98676ab7f3"><img src="data:image/webp;base64,UklGRq4AAABXRUJQVlA4IKIAAADQBQCdASpAACMAPzmEt1YvJ6UjsrSZmeAnCWcAAKHSSQNLuICmAZAAWBthkNzTpSSNLIApSkcAAP7umQ3b9OIC9LkYqgdtDjmdbO1CBYSMExraC8aX2kLVfMIpzKH0napo3uWqH9zWM/LAY5Q0j+TFr2FVVwTqUayd8kHczNCj+yYuRxALX+lUg5JPhc69PdK8LAdUqJV+5tI70JsjlIwAAAA=" alt="A clear glass icon-based diagram within a teal-blue framed, rounded rectangle, with text and arrows illustrating a transaction flow. The diagram, titled &#x22;Transaction&#x22; at the top in dark gray text, features two identical glass documents side-by-side. Beneath the left document, a small down arrow points to a reflective cylindrical object labeled &#x22;Events.&#x22; Beneath the right document, an identical down arrow points to an identical cylindrical object labeled &#x22;Aggregate.&#x22; The entire transparent glass setup is placed on a reflective white surface against a blurred, light studio background." width="1408" height="768"></picture></aside>
</p>
<p>
  I have been iterating on Event Sourcing and CQRS architectures for nearly a
  decade. When I first started with this pattern around 2016 and 2017, the
  implementation was considerably more complex — a lot of ceremony, a lot of
  infrastructure, and a lot of things that could go wrong. Over the years I have
  stripped it back again and again, and what I have arrived at now is something
  I'm genuinely happy with: a small, focused TypeScript library backed by DynamoDB
  that does exactly what it needs to and nothing more.
</p>
<p>
  The code is available at
  <a href="https://github.com/coderbyheart/aws-dynamodb-es-cqrs">github.com/coderbyheart/aws-dynamodb-es-cqrs</a>.
</p>
<h2>What ES+CQRS gives you</h2>
<p>
  The core idea is simple: instead of storing the current state of a thing, you
  store the sequence of events that produced that state. Event Sourcing (ES) is
  the "store events" part. CQRS (Command Query Responsibility Segregation) is the
  recognition that writing and reading are fundamentally different concerns that
  benefit from being handled separately.
</p>
<p>
  In practice this means that when someone publishes a blog post, you don't update
  a <code>published = true</code> column. You append a <code>BlogPostPublished</code> event to the event
  log. The current state — the aggregate — is derived by replaying those events.
  This gives you a full audit trail for free, the ability to rebuild projections,
  and a natural boundary for business logic.
</p>
<h2>The anatomy of an event</h2>
<p>
  Every event in the system is an <code>AggregateEvent</code>. The type is intentionally
  minimal:
</p>
<pre class="language-typescript"><code class="language-typescript code-highlight"><span class="code-line"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">AggregateEvent</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
</span><span class="code-line">  eventId<span class="token operator">:</span> <span class="token constant">ULID</span><span class="token punctuation">;</span>
</span><span class="code-line">  eventName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
</span><span class="code-line">  aggregateName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
</span><span class="code-line">  aggregateId<span class="token operator">:</span> <span class="token constant">ULID</span><span class="token punctuation">;</span>
</span><span class="code-line">  aggregateVersion<span class="token operator">:</span> AggregateVersion<span class="token punctuation">;</span>
</span><span class="code-line">  actorId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></code></pre>
<p>
  A few things worth noting here. <code>eventId</code> and <code>aggregateId</code> use
  <a href="https://github.com/ulid/spec">ULIDs</a> — universally unique, lexicographically
  sortable identifiers. This means events stored in DynamoDB are naturally ordered
  by creation time without needing a separate timestamp index. <code>aggregateVersion</code>
  is an incrementing integer that starts at <code>1</code> and tracks how many events have
  been applied to a given aggregate. <code>actorId</code> records who or what triggered the
  event.
</p>
<p>
  Domain events extend this base type and add their own payload fields. A
  <code>BlogPostCreatedEvent</code>, for example, adds <code>title</code> and whatever else the domain
  needs.
</p>
<h2>Reducing events to state</h2>
<p>
  The event sourcing half of the pattern lives in <code>reduceEvents</code>. It takes an
  <code>applyEvent</code> function and returns a function that folds a list of events into an
  aggregate:
</p>
<pre class="language-typescript"><code class="language-typescript code-highlight"><span class="code-line"><span class="token keyword">export</span> <span class="token keyword">const</span> reduceEvents <span class="token operator">=</span>
</span><span class="code-line">  <span class="token operator">&#x3C;</span><span class="token constant">A</span> <span class="token keyword">extends</span> <span class="token class-name">Record<span class="token operator">&#x3C;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">any</span><span class="token operator">></span></span> <span class="token operator">&#x26;</span> <span class="token punctuation">{</span> $meta<span class="token operator">:</span> AggregateMeta <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">(</span>
</span><span class="code-line">    applyEvent<span class="token operator">:</span> ApplyEventFn<span class="token operator">&#x3C;</span><span class="token constant">A</span><span class="token operator">></span><span class="token punctuation">,</span>
</span><span class="code-line">  <span class="token punctuation">)</span><span class="token operator">:</span> ApplyEventsFn<span class="token operator">&#x3C;</span><span class="token constant">A</span><span class="token operator">></span> <span class="token operator">=></span>
</span><span class="code-line">  <span class="token punctuation">(</span>events<span class="token operator">:</span> <span class="token builtin">Array</span><span class="token operator">&#x3C;</span>AggregateEvent<span class="token operator">></span><span class="token punctuation">,</span> aggregate<span class="token operator">?</span><span class="token operator">:</span> <span class="token constant">A</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token keyword">if</span> <span class="token punctuation">(</span>events<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"No events to reduce!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token keyword">return</span> events<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">reduce</span><span class="token generic class-name"><span class="token operator">&#x3C;</span><span class="token constant">A</span> <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>agg<span class="token punctuation">,</span> event<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line">      <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token function">applyEvent</span><span class="token punctuation">(</span>event<span class="token punctuation">,</span> agg<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">      <span class="token keyword">if</span> <span class="token punctuation">(</span>result <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span>
</span><span class="code-line">        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span>
</span><span class="code-line">          <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Unhandled event: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>event<span class="token punctuation">.</span>eventName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> (aggregateVersion: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>event<span class="token punctuation">.</span>aggregateVersion<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
</span><span class="code-line">        <span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">      <span class="token keyword">return</span> result<span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token punctuation">}</span><span class="token punctuation">,</span> aggregate<span class="token punctuation">)</span> <span class="token keyword">as</span> <span class="token constant">A</span><span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></code></pre>
<p>
  The domain-specific reducer wires this together. The blog post reducer handles
  three events — created, published, and title changed — using typed event guards
  (<code>isNamedEvent</code>) and assertions (<code>assertAggregateEvent</code>) to make the TypeScript
  types precise:
</p>
<pre class="language-typescript"><code class="language-typescript code-highlight"><span class="code-line"><span class="token keyword">export</span> <span class="token keyword">const</span> blogpostReducer <span class="token operator">=</span> <span class="token generic-function"><span class="token function">reduceEvents</span><span class="token generic class-name"><span class="token operator">&#x3C;</span>BlogPostAggregate<span class="token operator">></span></span></span><span class="token punctuation">(</span>
</span><span class="code-line">  <span class="token punctuation">(</span>event<span class="token punctuation">,</span> aggregate<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isBlogPostCreatedEvent</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">      <span class="token keyword">return</span> <span class="token punctuation">{</span>
</span><span class="code-line">        $meta<span class="token operator">:</span> <span class="token function">fromEvent</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line">        authorId<span class="token operator">:</span> event<span class="token punctuation">.</span>actorId<span class="token punctuation">,</span>
</span><span class="code-line">        title<span class="token operator">:</span> event<span class="token punctuation">.</span>title<span class="token punctuation">,</span>
</span><span class="code-line">      <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token punctuation">}</span>
</span><span class="code-line">    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isBlogPostPublishedEvent</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">      <span class="token function">assertAggregateEvent</span><span class="token punctuation">(</span>aggregate<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">      <span class="token keyword">return</span> <span class="token punctuation">{</span>
</span><span class="code-line">        <span class="token operator">...</span>aggregate<span class="token punctuation">,</span>
</span><span class="code-line">        $meta<span class="token operator">:</span> <span class="token function">updateFromEvent</span><span class="token punctuation">(</span>aggregate<span class="token punctuation">.</span>$meta<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line">        isPublic<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
</span><span class="code-line">      <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token punctuation">}</span>
</span><span class="code-line">    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isBlogPostTitleChangedEvent</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">      <span class="token function">assertAggregateEvent</span><span class="token punctuation">(</span>aggregate<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">      <span class="token keyword">return</span> <span class="token punctuation">{</span>
</span><span class="code-line">        <span class="token operator">...</span>aggregate<span class="token punctuation">,</span>
</span><span class="code-line">        $meta<span class="token operator">:</span> <span class="token function">updateFromEvent</span><span class="token punctuation">(</span>aggregate<span class="token punctuation">.</span>$meta<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line">        title<span class="token operator">:</span> event<span class="token punctuation">.</span>title<span class="token punctuation">,</span>
</span><span class="code-line">      <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token punctuation">}</span>
</span><span class="code-line">    <span class="token keyword">return</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span><span class="token punctuation">,</span>
</span><span class="code-line"><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
<p>
  The <code>assertAggregateEvent</code> call on the update and publish handlers is doing
  something important: it verifies that an aggregate already exists before trying
  to apply a state-changing event. You cannot publish a blog post that was never
  created.
</p>
<h2>Commands: the write side</h2>
<p>
  Commands live on the write side of the CQRS split. They are plain async
  functions that take some input and a persistence function, validate
  preconditions against the current aggregate state, produce an event, apply it
  through the reducer, persist both the resulting aggregate and the event, and
  return the updated aggregate.
</p>
<p>Here is what creating a blog post looks like:</p>
<pre class="language-typescript"><code class="language-typescript code-highlight"><span class="code-line"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">createBlogPostCommand</span> <span class="token operator">=</span>
</span><span class="code-line">  <span class="token punctuation">(</span>
</span><span class="code-line">    persistBlogPost<span class="token operator">:</span> PersistBlogPostFn<span class="token punctuation">,</span>
</span><span class="code-line">    reducer<span class="token operator">:</span> ApplyEventsFn<span class="token operator">&#x3C;</span>BlogPostAggregate<span class="token operator">></span><span class="token punctuation">,</span>
</span><span class="code-line">  <span class="token punctuation">)</span> <span class="token operator">=></span>
</span><span class="code-line">  <span class="token keyword">async</span> <span class="token punctuation">(</span>
</span><span class="code-line">    data<span class="token operator">:</span> Omit<span class="token operator">&#x3C;</span>BlogPostAggregate<span class="token punctuation">,</span> <span class="token string">"$meta"</span> <span class="token operator">|</span> <span class="token string">"authorId"</span><span class="token operator">></span><span class="token punctuation">,</span>
</span><span class="code-line">    actorId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
</span><span class="code-line">  <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&#x3C;</span>BlogPostAggregate<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token keyword">const</span> id <span class="token operator">=</span> <span class="token function">ulid</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> <span class="token constant">ULID</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token keyword">const</span> event<span class="token operator">:</span> BlogPostCreatedEvent <span class="token operator">=</span> <span class="token punctuation">{</span>
</span><span class="code-line">      eventId<span class="token operator">:</span> <span class="token function">ulid</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> <span class="token constant">ULID</span><span class="token punctuation">,</span>
</span><span class="code-line">      eventName<span class="token operator">:</span> EventNames<span class="token punctuation">.</span>BlogPostCreated<span class="token punctuation">,</span>
</span><span class="code-line">      aggregateName<span class="token operator">:</span> AggregateNames<span class="token punctuation">.</span>BlogPost<span class="token punctuation">,</span>
</span><span class="code-line">      aggregateId<span class="token operator">:</span> id<span class="token punctuation">,</span>
</span><span class="code-line">      aggregateVersion<span class="token operator">:</span> v1<span class="token punctuation">,</span>
</span><span class="code-line">      actorId<span class="token punctuation">,</span>
</span><span class="code-line">      <span class="token operator">...</span>data<span class="token punctuation">,</span>
</span><span class="code-line">    <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token keyword">const</span> applied <span class="token operator">=</span> <span class="token function">reducer</span><span class="token punctuation">(</span><span class="token punctuation">[</span>event<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token keyword">await</span> <span class="token function">persistBlogPost</span><span class="token punctuation">(</span>applied<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token keyword">return</span> applied<span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></code></pre>
<p>
  The reducer is injected alongside the persist function rather than imported
  directly. This keeps commands decoupled from any specific reducer implementation
  and makes them straightforward to test in isolation. A command for updating an
  existing aggregate (like <code>publishBlogPostCommand</code>) follows the same pattern:
  inject both the persist function and the reducer, load the current state, verify
  preconditions, produce a new event at the next version. Concurrency conflicts
  are detected at the persistence layer using optimistic locking, so you get a
  <code>ConflictError</code> if two commands race on the same aggregate.
</p>
<h2>Persistence: one transaction, two tables</h2>
<p>
  The most interesting part of this design is the persistence layer. Everything
  happens in a single DynamoDB <code>TransactWriteItems</code> call that touches two tables
  atomically:
</p>
<ol>
  <li>
    <p>
      The <strong>aggregate table</strong> is upserted with an optimistic concurrency check. For
      a new aggregate, the condition is <code>attribute_not_exists(aggregateId)</code>. For an
      update, it is <code>#version = :prevVersion</code>. If either condition fails, DynamoDB
      cancels the whole transaction and the application surfaces a conflict error.
    </p>
  </li>
  <li>
    <p>
      The <strong>events table</strong> receives a new <code>Put</code> item containing the full event
      payload, plus a derived <code>eventTs</code> (an ISO timestamp decoded from the ULID)
      for convenience.
    </p>
  </li>
</ol>
<p>
  The error handling checks cancellation reasons across both transaction slots — a
  <code>DuplicateItem</code> on the events table (index <code>1</code>) is just as meaningful as a
  <code>ConditionalCheckFailed</code> on the aggregate (index <code>0</code>), and both produce an
  actionable error rather than a raw SDK exception:
</p>
<pre class="language-typescript"><code class="language-typescript code-highlight"><span class="code-line"><span class="token keyword">await</span> db<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>
</span><span class="code-line">  <span class="token keyword">new</span> <span class="token class-name">TransactWriteItemsCommand</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
</span><span class="code-line">    TransactItems<span class="token operator">:</span> <span class="token punctuation">[</span>
</span><span class="code-line">      <span class="token punctuation">{</span> Update<span class="token operator">:</span> <span class="token function">toUpdate</span><span class="token punctuation">(</span>aggregate<span class="token punctuation">,</span> aggregateTableName<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
</span><span class="code-line">      <span class="token punctuation">{</span>
</span><span class="code-line">        Put<span class="token operator">:</span> <span class="token punctuation">{</span>
</span><span class="code-line">          TableName<span class="token operator">:</span> eventsTableName<span class="token punctuation">,</span>
</span><span class="code-line">          Item<span class="token operator">:</span> <span class="token function">marshall</span><span class="token punctuation">(</span>
</span><span class="code-line">            <span class="token punctuation">{</span>
</span><span class="code-line">              <span class="token operator">...</span>event<span class="token punctuation">,</span>
</span><span class="code-line">              eventTs<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token function">decodeTime</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>eventId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line">            <span class="token punctuation">}</span><span class="token punctuation">,</span>
</span><span class="code-line">            <span class="token punctuation">{</span> removeUndefinedValues<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
</span><span class="code-line">          <span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line">        <span class="token punctuation">}</span><span class="token punctuation">,</span>
</span><span class="code-line">      <span class="token punctuation">}</span><span class="token punctuation">,</span>
</span><span class="code-line">    <span class="token punctuation">]</span><span class="token punctuation">,</span>
</span><span class="code-line">  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line"><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token comment">// ...</span>
</span><span class="code-line"><span class="token keyword">if</span> <span class="token punctuation">(</span>err <span class="token keyword">instanceof</span> <span class="token class-name">TransactionCanceledException</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">.</span>CancellationReasons<span class="token operator">?.</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">?.</span>Code <span class="token operator">===</span> <span class="token string">"ConditionalCheckFailed"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span>
</span><span class="code-line">      <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Failed to persist "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>aggregate<span class="token punctuation">.</span>$meta<span class="token punctuation">.</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" due to version conflict!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
</span><span class="code-line">    <span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span>
</span><span class="code-line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>
</span><span class="code-line">    err<span class="token punctuation">.</span>CancellationReasons<span class="token operator">?.</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">?.</span>Code <span class="token operator">===</span> <span class="token string">"DuplicateItem"</span> <span class="token operator">||</span>
</span><span class="code-line">    err<span class="token punctuation">.</span>CancellationReasons<span class="token operator">?.</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token operator">?.</span>Code <span class="token operator">===</span> <span class="token string">"DuplicateItem"</span>
</span><span class="code-line">  <span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span>
</span><span class="code-line">      <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Failed to persist "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>aggregate<span class="token punctuation">.</span>$meta<span class="token punctuation">.</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" due to duplicate item!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
</span><span class="code-line">    <span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span>
</span><span class="code-line"><span class="token punctuation">}</span>
</span></code></pre>
<p>
  Because the aggregate and the event are written in one transaction, they are
  always in sync. You never end up in a state where an event was recorded but the
  aggregate was not updated, or vice versa.
</p>
<h2>The read side</h2>
<p>
  Queries are entirely separate from commands — that is the whole point of the CQR
  split. The read side has two shapes:
</p>
<p>
  <strong>Current state</strong> — fetch the latest aggregate projection directly from the
  aggregate table. Because <code>persistDynamoDB</code> keeps the aggregate table current on
  every write, reads are a simple <code>GetItem</code> or <code>Query</code> against a well-known key
  schema. No event replay needed at query time.
</p>
<p>
  <strong>Event stream</strong> — use <code>listAggregateEventsDynamoDB</code> to retrieve all events for
  a given aggregate in order. This is useful for rebuilding projections, feeding
  downstream consumers, or debugging. DynamoDB Streams integration is also
  covered: <code>extractEventsFromDynamoDBEvent</code> parses stream records back into typed
  <code>AggregateEvent</code> objects so they can be forwarded to Lambda functions or other
  processors.
</p>
<h2>What makes this simpler than the 2016 version</h2>
<p>
  The earlier versions of this pattern wrote the event to the event store first,
  then relied on a separate asynchronous step — typically a DynamoDB Stream
  feeding a Lambda — to update the aggregate projection. This worked, but it
  created three problems that I kept having to work around.
</p>
<p>
  The first was a <strong>window of inconsistency</strong>. Between the moment an event was
  written and the moment the Lambda finished updating the aggregate, the two
  stores were out of sync. In practice this window was small, but it was real. If
  the Lambda failed — a cold start timeout, a throttle, a transient DynamoDB error
  — the aggregate could end up permanently behind the event log. You needed
  dead-letter queues, retry logic, and monitoring to catch and recover from these
  failures.
</p>
<p>
  The second was <strong>latency on the read side</strong>. Because the aggregate projection
  was updated asynchronously, a caller that wrote an event and immediately read
  back the aggregate might get stale data. You had to either accept eventual
  consistency or build compensating logic to handle it.
</p>
<p>
  The third was <strong>CDK infrastructure overhead</strong>. Every aggregate needed a Stream
  enabled on its events table, a Lambda to consume it, an event source mapping,
  IAM roles, a DLQ, and CloudWatch alarms. This was not a huge amount of code, but
  it added up and it was all load-bearing — getting any of it wrong meant silent
  data loss.
</p>
<p>
  The current approach sidesteps all of this with a single <code>TransactWriteItems</code>
  call. The event and the aggregate projection are written atomically. Either both
  succeed or neither does. There is no async gap, no eventual consistency on the
  write path, and no extra CDK constructs to manage. The aggregate table is always
  in sync with the event store by construction.
</p>
<p>
  The tradeoff is that you give up the ability to fan out to multiple projections
  in the same write path — but in practice I have found that DynamoDB Streams off
  the events table is still the right mechanism for that, and you can add it when
  you actually need it rather than building it upfront for every aggregate.
</p>
<hr>
<p>
  The full source, including a working test suite that runs against DynamoDB
  Local, is at
  <a href="https://github.com/coderbyheart/aws-dynamodb-es-cqrs">github.com/coderbyheart/aws-dynamodb-es-cqrs</a>.
  The package is also published to npm as
  <a href="https://www.npmjs.com/package/@coderbyheart/aws-dynamodb-es-cqrs"><code>@coderbyheart/aws-dynamodb-es-cqrs</code></a>
  if you want to use it directly.
</p>
]]></content:encoded>
		</item>
		<item>
			<title>Do not expect an LLM to think</title>
			<link>https://coderbyheart.com/do-not-expect-an-llm-to-think</link>
			<guid isPermaLink="true">https://coderbyheart.com/do-not-expect-an-llm-to-think</guid>
			<pubDate>Thu, 14 May 2026 09:37:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>LLMs are excellent at scaffolding code that follows an existing pattern. They are not equipped to design new domain concepts. When you let one do design work, you get code that looks right, tests that pass, and an implementation that quietly misses the point. Worse: you own none of the understanding, so when something breaks, you&apos;re debugging a library you didn&apos;t write.</description>
			<content:encoded><![CDATA[
<p>
  <aside class="hero"><picture style="aspect-ratio: 1200 / 627" data-cdn><source srcset="https://7w7z6ydf2htamqdsm6nbxm7sma0nkltc.lambda-url.eu-central-1.on.aws/coderbyheart.com/media/cac2fb21a32b9d515956a17ddff6805d7319a5b7c1bf718553e9ffe852e94381"><img src="data:image/webp;base64,UklGRm4AAABXRUJQVlA4IGIAAACwBACdASpAACEAPzmMuFOvKiwisBgKqeAnCWkAAC51wZAuzJRW/SfwX+zB0pOAAP7i4nneS9JtvNv92Q50lW+v/laNeCeap/JVwyZBUxSBCTHwHehuLE20RaMiGb3e4gAAAA==" alt="confident green tests on the left, a broken invoice on the right" width="1200" height="627"></picture></aside>
</p>
<blockquote>
  <p>
    <strong>tl;dr</strong> LLMs are excellent at scaffolding code that follows an existing
    pattern. They are not equipped to design new domain concepts. When you let one
    do design work, you get code that looks right, tests that pass, and an
    implementation that quietly misses the point. Worse: you own none of the
    understanding, so when something breaks, you're debugging a library you didn't
    write.
  </p>
</blockquote>
<p>
  When all the tests are green but the implementation is wrong, you have a
  problem. And you have made that problem yourself.
</p>
<p>
  This week I introduced a new concept into an existing billing system. We
  currently bill on a per-project basis. The new requirement was to introduce
  <em>organizations</em>: an organization owns multiple projects, and instead of one
  invoice per project, the organization owner gets a single consolidated invoice.
</p>
<p>Same billing rules. Same pricing logic. Just applied to a higher-level entity.</p>
<p>
  The existing implementation was well-tested and solid. This looked like a
  perfect LLM task: the pattern is established, the tests are clear, the scope is
  bounded. I asked the LLM to introduce the organization concept, told it to look
  at the existing tests, and make it work for an organization owning multiple
  projects.
</p>
<p>
  The initial results were promising. New modules, new method names, new test
  files, good coverage. There was a small bit of hand-holding needed around how we
  identify organizations—they use a different ID format than projects—but overall
  it looked like a win.
</p>
<p>
  Then I started wiring the new domain logic into the actual consumers, and the
  cracks appeared.
</p>
<h2>The tests were lying</h2>
<p>
  The issues were not dramatic. No obvious failures. They were <em>subtle</em>—quiet
  mismatches between what the tests assumed and what the business actually
  required. I went back to the domain implementation to fix something. Then again.
  Then again. Four times in total.
</p>
<p>
  A concrete example: our billing has a minimum charge rule. If your total monthly
  usage comes to less than $2.00, you still pay $2.00.
</p>
<p>
  In the per-project billing, this is simple: sum the usage, apply the rate, check
  against the floor. The LLM ported this rule to the organization level by
  applying it <em>per project</em>. Three projects using $0.10 each would result in a
  $6.00 bill instead of $2.00, because each project individually hits the minimum.
</p>
<p>
  The minimum charge belongs on the organization invoice—the thing the customer
  actually pays—not on each individual project. But the concept of <em>the invoice</em>
  was implicit in the per-project implementation. It was just the natural unit. At
  the organization level it becomes a derived concept, only meaningful after
  summing across all projects. The LLM didn't understand that this rule needed to
  be lifted to a higher abstraction level. It only saw the mechanics: here is a
  rule, apply it at each step.
</p>
<p>
  The tests didn't catch this because the generated test data wasn't adversarial.
  Two projects, charges above the minimum, everything sums correctly—all green.
  The discrepancy only surfaced when I ran real customer usage data through both
  the old and new implementations side by side.
</p>
<h2>Debugging code you didn't write</h2>
<p>What I underestimated is the cost of <em>not</em> having written the code yourself.</p>
<p>
  Every time I hit a bug I was in the position of debugging a third-party library
  I didn't write and don't fully understand. I'd look at the symptom, form a
  hypothesis, and ask the LLM for a targeted fix—without truly grasping whether
  that fix would hold in other situations. This is the <code>console.log</code> debugging
  mode: poking at the surface, observing behavior, inferring structure. Slow,
  fragile, and deeply unsatisfying.
</p>
<p>
  A feedback loop makes it worse: each fix feels small. <em>Just change this one
thing, then it will work.</em> So you keep delegating back to the LLM. But the LLM
  can only act on what you describe. It doesn't understand your goal—it
  understands your instruction. It will dutifully add a parameter, loop over
  projects, apply the calculation, and miss the point entirely.
</p>
<h2>Scaffolding vs. design</h2>
<p>The mistake was misclassifying the task.</p>
<p>
  There are things LLMs are excellent at in an existing codebase. Scaffolding a
  new REST route that follows the same pattern as the 25 others in the project?
  Perfect. Generate the handler, the CDK construct, wire it into the existing
  infrastructure. The output is obviously correct by inspection because the
  pattern is unambiguous.
</p>
<p>
  Introducing a new domain concept that changes how the entire billing model is
  structured is a <em>design</em> task. It requires understanding intent. It requires
  knowing which implicit assumptions need to become explicit, which rules belong
  at which level of abstraction, what the code is <em>for</em>—not just what it <em>does</em>.
</p>
<p>LLMs can replicate structure. They cannot reason about intent.</p>
<h2>What I should have done</h2>
<p>Written it myself.</p>
<p>
  Not because the LLM couldn't produce working-looking code—it clearly could. But
  because the process of writing domain logic <em>is</em> how you discover the domain.
  The resistance you feel when you try to implement a rule at the wrong
  abstraction level, the moment you realize the minimum charge can't live on the
  project anymore—those are discoveries that only happen when <em>you</em> are making the
  decisions.
</p>
<p>
  By outsourcing the implementation, I outsourced the understanding. And
  understanding is not optional when things go wrong.
</p>
<p>
  The LLM saved me roughly two hours of initial implementation and cost me several
  times that in confused debugging and rework. The irony is that the task that
  looked <em>most</em> suited to LLM delegation—structured, well-precedented, bounded—
  turned out to require exactly the kind of judgment LLMs don't have.
</p>
<p>Use an LLM to follow a pattern. Don't use it to design one.</p>
]]></content:encoded>
		</item>
		<item>
			<title>Readable Error Messages</title>
			<link>https://coderbyheart.com/readable-error-messages</link>
			<guid isPermaLink="true">https://coderbyheart.com/readable-error-messages</guid>
			<pubDate>Sun, 04 Jan 2026 17:36:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>Use RFC 7807 &quot;Problem Detail&quot; to document API errors.</description>
			<content:encoded><![CDATA[
<blockquote>
  <p>
    Error messages are the only piece of documentation you can be sure people will
    read when it is relevant. Write them accordingly.<br>— <a href="https://twitter.com/bethcodes/status/1303409425554665474"><em>Beth Andres-Beck</em></a>
  </p>
</blockquote>
<p>
  Use <a href="https://tools.ietf.org/html/rfc7807">RFC 7807 "Problem Detail"</a> to document
  API errors.
</p>
]]></content:encoded>
		</item>
		<item>
			<title>Separate workflows through events</title>
			<link>https://coderbyheart.com/separate-workflows-through-events</link>
			<guid isPermaLink="true">https://coderbyheart.com/separate-workflows-through-events</guid>
			<pubDate>Fri, 02 Jan 2026 20:05:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>Build scalable systems by separating concerns.</description>
			<content:encoded><![CDATA[
<p>Build scalable systems by separating concerns.</p>
<h2>Bad</h2>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token keyword">class</span> <span class="token class-name">UserManager</span> <span class="token punctuation">{</span>
</span><span class="code-line">  <span class="token keyword">async</span> <span class="token function">createUser</span><span class="token punctuation">(</span><span class="token parameter">email</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token keyword">const</span> id <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">userRepo</span><span class="token punctuation">.</span><span class="token method function property-access">insert</span><span class="token punctuation">(</span>email<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token keyword control-flow">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">upgradeAccount</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> <span class="token string">"trial"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token keyword control-flow">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">sendConfirmationEmail</span><span class="token punctuation">(</span>email<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token keyword control-flow">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">notifySlack</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Woot! </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>email<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> has registered</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span>
</span><span class="code-line"><span class="token punctuation">}</span>
</span></code></pre>
<p>
  The UserManager does too many things and needs to know all these dependencies.
  There is no clear limit to what could be added there in the future, that needs
  to be done when a user is created. This can increase the runtime of this one
  method further and further and couples systems that not need to be coupled.
</p>
<h2>Good</h2>
<p>
  Instead have all methods that change the state of the system only do exactly on
  operation and use Events to have other components get notified. This enables to
  run these secondary operations in a separate process and even in an environment
  which is better suited for them.
</p>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token keyword">class</span> <span class="token class-name">UserManager</span> <span class="token punctuation">{</span>
</span><span class="code-line">  <span class="token keyword">async</span> <span class="token function">createUser</span><span class="token punctuation">(</span><span class="token parameter">email</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token keyword">const</span> id <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">userRepo</span><span class="token punctuation">.</span><span class="token method function property-access">insert</span><span class="token punctuation">(</span>email<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">eventBus</span><span class="token punctuation">.</span><span class="token method function property-access">publish</span><span class="token punctuation">(</span><span class="token string">"UserCreatedEvent"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> id<span class="token punctuation">,</span> email <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span>
</span><span class="code-line"><span class="token punctuation">}</span>
</span><span class="code-line">
</span><span class="code-line"><span class="token keyword">class</span> <span class="token class-name">TransactionalMailer</span> <span class="token punctuation">{</span>
</span><span class="code-line">  <span class="token function">construct</span><span class="token punctuation">(</span><span class="token parameter">eventBus</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">    eventBus<span class="token punctuation">.</span><span class="token method function property-access">on</span><span class="token punctuation">(</span><span class="token string">"UserCreated"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">name<span class="token punctuation">,</span> <span class="token punctuation">{</span> email <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span>
</span><span class="code-line">      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">sendConfirmationEmail</span><span class="token punctuation">(</span>email<span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line">    <span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span>
</span><span class="code-line"><span class="token punctuation">}</span>
</span></code></pre>
<p>This is enabled by implementing the system using Event Sourcing.</p>
]]></content:encoded>
		</item>
		<item>
			<title>Do not change two different things in one Pull Request</title>
			<link>https://coderbyheart.com/do-not-change-two-different-things-in-one-pull-request</link>
			<guid isPermaLink="true">https://coderbyheart.com/do-not-change-two-different-things-in-one-pull-request</guid>
			<pubDate>Thu, 01 Jan 2026 23:10:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>A pull request should only address one change at a time.</description>
			<content:encoded><![CDATA[
<p>A Pull Request should only address one change at a time.</p>
<p>
  Reviewers might not have an in-depth understanding of how things relate to each
  other in a project so do not mix conceptually different changes in one PR.
</p>
<p>Some examples of what not to do:</p>
<ul>
  <li>
    fix a bug AND update all dependencies. If a bug fix requires a new dependency
    or an update to one, then only add this single dependency change.
  </li>
  <li>
    implement a new feature AND fix a small bug on an unrelated file. Those should
    be two PRs.
  </li>
</ul>
]]></content:encoded>
		</item>
		<item>
			<title>Keep code left</title>
			<link>https://coderbyheart.com/keep-code-left</link>
			<guid isPermaLink="true">https://coderbyheart.com/keep-code-left</guid>
			<pubDate>Thu, 01 Jan 2026 23:10:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>Make code more readable.</description>
			<content:encoded><![CDATA[
<p>This long wrapping in a if / else clause makes the code hard to read.</p>
<h2>Bad</h2>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>condition<span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">  <span class="token function">doWork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token punctuation">}</span> <span class="token keyword control-flow">else</span> <span class="token punctuation">{</span>
</span><span class="code-line">  <span class="token keyword control-flow">throw</span> <span class="token known-class-name class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Condition not met!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token punctuation">}</span>
</span></code></pre>
<h2>Good</h2>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>condition<span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">  <span class="token keyword control-flow">throw</span> <span class="token known-class-name class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Condition not met!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token punctuation">}</span>
</span><span class="code-line"><span class="token function">doWork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
<h2>Stop nesting promises</h2>
<p><a href="https://twitter.com/manekinekko/status/855824609299636230">Another example</a>:</p>
<h3>Bad</h3>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line">  <span class="token function">getMoreData</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">b</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token function">getMoreData</span><span class="token punctuation">(</span>b<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">c</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line">      <span class="token function">getMoreData</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">d</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line">        <span class="token function">getMoreData</span><span class="token punctuation">(</span>d<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line">          <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
<h3>Better</h3>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</span><span class="code-line">  <span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token function">getMoreData</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">)</span>
</span><span class="code-line">  <span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">b</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token function">getMoreData</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span>
</span><span class="code-line">  <span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">c</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token function">getMoreData</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">)</span>
</span><span class="code-line">  <span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">d</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token function">getMoreData</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">)</span>
</span><span class="code-line">  <span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
<h3>Even better</h3>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token keyword">const</span> a <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token keyword">const</span> b <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getMoreData</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token keyword">const</span> c <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getMoreData</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token keyword">const</span> d <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getMoreData</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token keyword">const</span> e <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getMoreData</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line"><span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
]]></content:encoded>
		</item>
		<item>
			<title>Rebase and ideally Squash instead of Merge Pull Requests</title>
			<link>https://coderbyheart.com/rebase-and-ideally-squash-instead-of-merge-pull-requests</link>
			<guid isPermaLink="true">https://coderbyheart.com/rebase-and-ideally-squash-instead-of-merge-pull-requests</guid>
			<pubDate>Thu, 01 Jan 2026 23:10:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>A pull request should only address one change at a time.</description>
			<content:encoded><![CDATA[
<p>
  You should rebase a branch against the main branch before "merging" it, so that
  there is no merge commit.
</p>
<p>
  While some
  <a href="https://medium.com/@fredrikmorken/why-you-should-stop-using-git-rebase-5552bee4fed1">consider rebasing to be an act of destroying the true history of a software project</a>,
  I think this is doing actually the opposite.
</p>
<p>
  What matters in terms of history is that we preserve the time when a set of
  changes became effective for the rest of the team. As long as a change is in an
  isolated branch on which only one developer works on, it cannot produce bugs for
  others (developers or users) thus it did not have the chance yet <em>to make
history</em> so to speak.
</p>
<p>
  By rebasing a PR against the main branch just before merging it will tell the
  real story: the changes will appear in this branch in the order they became
  <em>active</em> and this accurately reflects the time when others were made aware of
  these changes and also the time from when to entire codebase needed to deal with
  the changes introduced by them.
</p>
<h2>Squash merging</h2>
<p>
  Once a PR is done and has been approved, I like to squash all commits into one
  commit with a good and concise explanation. That one commit should ideally look
  like the PR itself: a PR on GitHub has a title and a decription, which explains
  the motivation for the change and lists what needs to be done (simple future).
  Turning this into a commit means telling the story of what the commit now does
  (in present tense).
</p>
<p>
  I consider squashing a PR into one commit to main an even better way to
  contribute changes to the main branch. I often have man small commits in a PR,
  which add tests, implement features, refactor. Having all these in the main
  branch does not really help other developers to quickly get the high-level
  summary of a change.
</p>
<h2>Cleaning up your commits for a PR</h2>
<p>
  This does not mean that all PRs should only have one commit (although
  <a href="https://github.com/coderbyheart/first-principles/issues/6">they rarely should have many</a>)
  it makes reviewing a PR easier if all the commits in a PR are condensed down to
  a set of distinct commit messages, even if they get squashed when being merged
  anyway. See this as an opportunity to describe to the reviewer <em>what</em> you did
  when implementing the change. It also enables a reviewer to view changes in a
  bigger PR with a context specific to a commit.
</p>
<h3>Re-tell the story of your PR</h3>
<p>
  Creating a clean set of distinct commits in a PR requires that atomic commits
  were made in the first place. It's a good habit while developing to <em>commit
early and commit often</em>: create many small commits, which you then later can
  combine into bigger ones. A great helper to achieve this is using
  <a href="https://gist.github.com/mattlewissf/9958704"><code>git add -p</code></a>, which will go
  through all current unstaged changes and let you decide which changed segment in
  a file to stage. <code>git add -p</code> allows you to create multiple commits from a
  situation where you have applied different changes while developing (a new
  feature here, but also a bug fix, and maybe some formatting, or changes to the
  CI configuration).
</p>
<blockquote>
  <p>
    Tip: when incorporating PR feedback into these commits,
    <a href="https://github.com/tummychow/git-absorb#elevator-pitch"><code>git-absorb</code></a> can
    help with that.
  </p>
</blockquote>
<h4>What if you have unrelated changes in the same commit</h4>
<p>
  You might encounter a situation where you accidentally combined unrelated
  changes in one commit. There is a way to split up these commits, but there is no
  reverse version of squash, it has to be done manually. See the <em>Splitting a
commit</em> section in the
  <a href="https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History">official Git documentation</a>.
  Here again, when in rebase, <code>git add -p</code> is your friend.
</p>
<h2>Resources</h2>
<ul>
  <li>https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History</li>
  <li>https://dev.to/maxwell_dev/the-git-rebase-introduction-i-wish-id-had</li>
  <li>https://gist.github.com/mattlewissf/9958704</li>
</ul>
]]></content:encoded>
		</item>
		<item>
			<title>Clean .gitignore files</title>
			<link>https://coderbyheart.com/clean-gitignore-files</link>
			<guid isPermaLink="true">https://coderbyheart.com/clean-gitignore-files</guid>
			<pubDate>Sun, 28 Dec 2025 11:38:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>A .gitignore in a project should only cover artifacts caused by the contained source code, not those caused by the personal choice of editor and/or environment of a developer.</description>
			<content:encoded><![CDATA[
<p>
  A <code>.gitignore</code> in a project should only cover artifacts caused by the contained
  source code, not those caused by the personal choice of editor and/or
  environment of a developer. Use
  <a href="https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer">a global git ignore file</a>
  for that.
</p>
<p>
  Yes, you can solve it also with a well maintained <code>.gitignore</code> file that covers
  everything in all projects but this can get
  <a href="https://github.com/github/gitignore">quite extensive to cover all cases</a>.
</p>
<p>
  I consider it noise if entries are in the <code>.gitignore</code> that have no relation to
  the specific project: I want a <strong>clean</strong> <code>.gitignore</code> file.
</p>
<p>
  Maintaining a global <code>.gitignore</code> file for <em>your personal setup</em> also has the
  advantage that it affects all existing and future projects on your machine, thus
  eliminating the possibility of accidentally committing those files. Otherwise
  you would need to remember to add your IDE files to EVERY projects <code>.gitignore</code>.
</p>
<p>
  Additionally, changes to the <code>.gitignore</code> of a project are not free: they
  trigger CI runs, releases, reviews. Having people constantly add their favourite
  rules creates noise. I don't want that.
</p>
<p>
  Having a huge catch-all <code>.gitignore</code> is no solution either, it will only cover
  known files. Additions trigger noise.
</p>
<p>
  Adding files to a git repository is a deliberate action, if developers add files
  that do not belong to a feature or change they need to be better educated about
  it. If this is done, an accident-preventing-gitignore is no longer needed.
</p>
<h2>Good example</h2>
<pre><code class="code-highlight"><span class="code-line">dist/
</span><span class="code-line">node_modules/
</span></code></pre>
<h2>Bad example</h2>
<pre><code class="code-highlight"><span class="code-line">.idea
</span><span class="code-line">.vscode
</span><span class="code-line">.DS_Store
</span></code></pre>
<h2>Tip</h2>
<p><a href="https://coderbyheart.com/have-a-local-folder-to-put-your-crap-in-which-wont-get-accidentally-committed/">Have a local folder to put your crap in which won't get accidentally committed</a></p>
]]></content:encoded>
		</item>
		<item>
			<title>Don&apos;t use global state like env variables in modules</title>
			<link>https://coderbyheart.com/dont-use-global-state-like-env-vars-in-modules</link>
			<guid isPermaLink="true">https://coderbyheart.com/dont-use-global-state-like-env-vars-in-modules</guid>
			<pubDate>Sun, 28 Dec 2025 11:38:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>This hides dependencies to configuration values and makes them hard to spot.</description>
			<content:encoded><![CDATA[
<p>
  Don't use global state like env variables in modules, only on the very top-level
  or in one central location. Pass it in the constructor.
</p>
<p>This hides dependencies to configuration values and makes them hard to spot.</p>
<h2>Bad</h2>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token keyword">class</span> <span class="token class-name">Foo</span> <span class="token punctuation">{</span>
</span><span class="code-line">  <span class="token function">doSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>process<span class="token punctuation">.</span><span class="token property-access">env</span><span class="token punctuation">.</span><span class="token constant">MY_ENV</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span>
</span><span class="code-line"><span class="token punctuation">}</span>
</span><span class="code-line">
</span><span class="code-line"><span class="token keyword">new</span> <span class="token class-name">Foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">doSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
<h2>Good</h2>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token keyword">class</span> <span class="token class-name">Foo</span> <span class="token punctuation">{</span>
</span><span class="code-line">  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">myEnv</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">myEnv</span> <span class="token operator">=</span> myEnv<span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span>
</span><span class="code-line">  <span class="token function">doSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line">    <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">myEnv</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line">  <span class="token punctuation">}</span>
</span><span class="code-line"><span class="token punctuation">}</span>
</span><span class="code-line">
</span><span class="code-line"><span class="token keyword">new</span> <span class="token class-name">Foo</span><span class="token punctuation">(</span>process<span class="token punctuation">.</span><span class="token property-access">env</span><span class="token punctuation">.</span><span class="token constant">MY_ENV</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">doSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
]]></content:encoded>
		</item>
		<item>
			<title>PRs should never contain formatting changes</title>
			<link>https://coderbyheart.com/keep-prs-free-of-formatting-changes</link>
			<guid isPermaLink="true">https://coderbyheart.com/keep-prs-free-of-formatting-changes</guid>
			<pubDate>Sun, 28 Dec 2025 11:38:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>It&apos;s important to have an automated way of formatting source code to eliminate discussions around style and remove noise from diffs.</description>
			<content:encoded><![CDATA[
<p>
  It's important to have an automated way of formatting source code to eliminate
  discussions around style and remove noise from diffs.
</p>
<h1>All code style guides must be enforceable by tools</h1>
<p>
  The best way to handle it is to fix all fixable errors using
  <a href="https://github.com/okonet/lint-staged">lint-staged</a> and
  <a href="https://prettier.io/">Prettier</a>. That way developers won't even notice. Good
  IDEs are able to apply prettier style guides automatically even during editing.
</p>
]]></content:encoded>
		</item>
		<item>
			<title>master is the wrong name for the main branch</title>
			<link>https://coderbyheart.com/master-is-the-wrong-name-for-your-branch</link>
			<guid isPermaLink="true">https://coderbyheart.com/master-is-the-wrong-name-for-your-branch</guid>
			<pubDate>Sun, 28 Dec 2025 11:38:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>I prefer saga branch over master because I think that this name (which means a long continuous recollection of histories) better reflects how development happens.</description>
			<content:encoded><![CDATA[
<p>
  I prefer <code>saga</code> branch over <code>master</code> because I think that this name (which means
  a long continuous recollection of histories) better reflects how development
  happens. A <em>master</em> conveys this idea of control over something, while it's
  actually the branches where development happens and which control what ends up
  in the main branch. A good name would also be <code>main</code>, or <code>trunk</code>.
</p>
<p><a href="https://twitter.com/_Gaeel_/status/1184740771548803077?s=19">How to change the default git branch</a></p>
<p>
  Note:
  <a href="https://twitter.com/tobie/status/1270290278029631489">The master terminology in Git is historically tied to the master/slave metaphor and not the master copy one</a>,
  <a href="https://mail.gnome.org/archives/desktop-devel-list/2019-May/msg00066.html">see</a>
  https://github.com/github/renaming
</p>
]]></content:encoded>
		</item>
		<item>
			<title>Method calls: favour argument bags over individual arguments</title>
			<link>https://coderbyheart.com/method-calls-favour-argument-bags-over-individual-arguments</link>
			<guid isPermaLink="true">https://coderbyheart.com/method-calls-favour-argument-bags-over-individual-arguments</guid>
			<pubDate>Sun, 28 Dec 2025 11:38:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>Bad: myMethod(arg1, arg2, arg3); Good: myMethod({ arg1, arg2, arg3 });</description>
			<content:encoded><![CDATA[
<p><strong>Bad</strong></p>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token function">myMethod</span><span class="token punctuation">(</span>arg1<span class="token punctuation">,</span> arg2<span class="token punctuation">,</span> arg3<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
<p><strong>Good</strong></p>
<pre class="language-javascript"><code class="language-javascript code-highlight"><span class="code-line"><span class="token function">myMethod</span><span class="token punctuation">(</span><span class="token punctuation">{</span> arg1<span class="token punctuation">,</span> arg2<span class="token punctuation">,</span> arg3 <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
<p><strong>Reasons</strong></p>
<ul>
  <li>
    this removes the
    <a href="https://dzone.com/articles/about-connascence">Connascence of Position (CoP)</a>
    because the order of arguments becomes irrelevant
  </li>
  <li>arguments to methods can easier be created programmatically</li>
</ul>
<p><strong>Watch out for</strong></p>
<ul>
  <li>removing support for currying</li>
</ul>
]]></content:encoded>
		</item>
		<item>
			<title>Don&apos;t say &quot;sorry&quot; for things you can&apos;t control</title>
			<link>https://coderbyheart.com/what-not-to-be-sorry-about</link>
			<guid isPermaLink="true">https://coderbyheart.com/what-not-to-be-sorry-about</guid>
			<pubDate>Sun, 28 Dec 2025 11:38:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>At work I don&apos;t like when people say sorry about something they can&apos;t control. I prefer they either just acknowledge it, or tell me what they are going to do about it.</description>
			<content:encoded><![CDATA[
<p>
  At work I don't like when people say sorry about something they can't control. I
  prefer they either just acknowledge it, or tell me what they are going to do
  about it.
</p>
]]></content:encoded>
		</item>
		<item>
			<title>Building serverless applications on AWS</title>
			<link>https://coderbyheart.com/serverless-on-aws</link>
			<guid isPermaLink="true">https://coderbyheart.com/serverless-on-aws</guid>
			<pubDate>Sun, 23 Mar 2025 09:00:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>This is the material I share with colleagues who want to learn the idiomatic way of building serverless applications on AWS</description>
			<content:encoded><![CDATA[
<p>
  I've been building serverless applications on AWS for over ten years. I was
  immediately hooked when
  <a href="https://aws.amazon.com/about-aws/whats-new/2014/11/13/introducing-aws-lambda/">AWS Lambda was launched in 2014</a>
  and have been using it as the main way to build web services using JavaScript,
  and later TypeScript ever since.
</p>
<p>
  For developers who come from classic, instance-based architectures, working with
  serverless is a huge paradigm shift. It's not natural, nor obvious. In this blog
  post I am sharing the current set of material I give to colleagues who want to
  learn the idiomatic way of building serverless.
</p>
<h2>The basics</h2>
<p>
  There are some fundamentals when building scalable web services, which apply
  both in instance-based and serverless architectures.
</p>
<p>
  <a href="https://coderbyheart.com/rest-in-practice-reading-guide">REST in practice</a> is a
  must read.
</p>
<p>
  <a href="https://12factor.net/">The Twelve-Factor App</a> provides important design aspects
  for web services in general, but this applies very well to serverless
  applications.
</p>
<p>
  In my talk
  <a href="https://youtu.be/E7t6BXGIZHk">Serverless Architecture for IoT on AWS</a> I
  explains the benefits of serverless in general in why it is especially suitable
  for IoT applications. I provide an introduction to serverless in general, and
  how it is implemented at the different cloud vendors (looking at AWS and Azure,
  where I have implemented the same application using the respective cloud's
  idiomatic way), and why it is especially relevant for IoT deployments.
</p>
<h2>Learning AWS serverless</h2>
<p>Of course, always check the official AWS resources on AWS Lambda:</p>
<ul>
  <li><a href="https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html">Best practices for working with AWS Lambda functions</a></li>
  <li><a href="https://aws.amazon.com/blogs/compute/category/compute/aws-lambda/">AWS Lambda blog</a></li>
</ul>
<p>
  For training my colleagues I have used the
  <a href="https://pages.awscloud.com/global-traincert-twitch-dev-hour-building-modern-applications.html">AWS Dev Hour: Building Modern Applications</a>
  training, with the added challenge of writing tests for it. My colleague Lena
  Kråkevik Haraldseid has a talk about her learnings from this exercise:
  <a href="https://www.youtube.com/watch?v=0qmAKKqLNsg">Testing serverless applications</a>.
</p>
<p>
  The recently released book
  <a href="https://coderbyheart.com/serverless-development-on-aws-review">Serverless Development on AWS</a>
  offers a great way to learn more about the potential of the serverless paradigm
  and the mindset shift needed to tap its full potential. It’s a must read for
  every developer that works with AWS because more and more powerful services on
  AWS are built from the ground up as serverless offering.
</p>
<h2>Testing</h2>
<p>
  One of the challenges when building serverless applications is testing. You are
  building on the shoulder of giants, and are using services that no longer run on
  your (CI) machine. In my talk
  <a href="https://coderbyheart.com/it-does-not-run-on-my-machine">It does not run on my machine: Integration testing a cloud-native application</a>
  I take you through the challenge of testing a cloud-native application. I cover
  the challenges when developing solutions on top of serverless components which
  you cannot run on your own machine and how I designed a BDD driven approach to
  run the integration tests.
</p>
<p>
  Here is AWS' best practice on testing Lambda:
  <a href="https://docs.aws.amazon.com/lambda/latest/dg/testing-guide.html">How to test serverless functions and applications</a>.
</p>
<h2>Reference projects I have built</h2>
<p>
  It's always best to look at actual code, so these are the open-source projects
  that are available which I have worked on:
</p>
<ul>
  <li><a href="https://github.com/hello-nrfcloud/backend">https://github.com/hello-nrfcloud/backend</a></li>
  <li><a href="https://github.com/hello-nrfcloud/map-backend">https://github.com/hello-nrfcloud/map-backend</a></li>
  <li><a href="https://github.com/NordicSemiconductor/asset-tracker-cloud-aws-js">https://github.com/NordicSemiconductor/asset-tracker-cloud-aws-js</a></li>
  <li><a href="https://github.com/NordicPlayground/thingy-rocks-cloud-aws-js">https://github.com/NordicPlayground/thingy-rocks-cloud-aws-js</a></li>
  <li><a href="https://github.com/teamstatus/aws-backend">https://github.com/teamstatus/aws-backend</a></li>
</ul>
]]></content:encoded>
		</item>
		<item>
			<title>Software Quality made this talk happen: push-to-deploy as the means to deal with uncertainty</title>
			<link>https://coderbyheart.com/push-to-deploy-as-the-means-to-deal-with-uncertainty</link>
			<guid isPermaLink="true">https://coderbyheart.com/push-to-deploy-as-the-means-to-deal-with-uncertainty</guid>
			<pubDate>Thu, 16 May 2024 13:30:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>This talk is an encouragement for developers to trust their skills and tools, but also invest in mastering them.
</description>
			<content:encoded><![CDATA[
<p><a href="https://www.youtube.com/embed/2KeBxWAdndo">https://www.youtube.com/embed/2KeBxWAdndo</a></p>
<p>
  I was first paid to write software in 1997, now 27 years ago, and if there is
  one skill that had the biggest impact on my career then it’s the ability to
  deliver working software that doesn’t break. This allows me to take vacation,
  invest back into my communities and initiatives that I want to support,
  self-development, and to have time for self-, and family care.
</p>
<p>
  In this talk I want to reinforce your love for software quality by sharing how I
  helps me in my day to day work where I balance my efforts between too many
  projects but still manage to be both and innovative software engineer, and
  deliver solutions that do not break when it matters most through setting up all
  projects and teams that I work with around one main principle: push-to-deploy.
</p>
<p>
  In order to achieve this, confidence is needed, which can only manifest if we
  know that our changes will not break the system. And in an environment that
  becomes more and more uncertain and volatile every day, there is only one
  solution that has stood the test of time: writing tests first. And its beauty,
  especially with outside-in-test-driven-development, is that it helps to keep the
  focus on delivering value to the end-user. Over the last years I have added
  Behavior-driven development to my quality toolbox because it decouples the
  implementation of the tests from the implementation of the application. Testing
  through the public API of a system ensures that there are no unexpected changes
  for the user.
</p>
<p>
  As I will share, I have a strict focus on user stories when building our
  products, because it is the only way that ensures that all stakeholders can
  participate and contribute to a product. Because in the end a successful
  software is always built in collaboration with people who contribute source-code
  and those who contribute in other ways.
</p>
<p>
  Enabling the collaboration of all stakeholders is a very important part of my
  job, and I have been able to achieve this through using a walking skeleton
  approach: delivering working minimal products from the first day. This way
  developers like me can make sure to be involved in the discussion with product
  people and customers early on in a projects' life-cycle. And in my experience
  only the real product (and not even a click dummy) will truly create shared
  understanding with all stakeholders. Because time is always short and we know
  that every prototype ends up in production we have to make sure that even our
  prototypes are of high quality; which is where using quality in software
  development to achieve continuous delivery (CD) with confidence - really shine.
</p>
<p>
  The slides are available
  <a href="https://drive.google.com/file/d/1ZrrTc4YdN0C23C1GOXilOaD4eCfsplYO/view?usp=sharing">here</a>.
</p>
<h2>Video transcript</h2>
<p>Thanks for joining me in this talk.</p>
<p>I can be here because software quality is the story of my professional career.</p>
<p>
  And given this is the 10th edition of New Crafts, I thought it would be time to
  remind you why we do what we do as crafters.
</p>
<p>
  My story goes back till 2012 when I became a crafter, but now I have colleagues
  who could be my kids if I had them.
</p>
<p>
  Like they're in their early twenties and they, they study, or they become
  juniors and they are the next generation of software engineers.
</p>
<p>And we as crafters need to think again like, why are we doing this?</p>
<p>And we need to explain this.</p>
<p>And this is, this is what I would like to share with you.</p>
<p>
  This is a talk that is very personal, so not everything that is in there might
  apply to you, so keep that in mind.
</p>
<p>But I hope you can take away a few things in there.</p>
<p>So, a little bit about me.</p>
<p>I am an R&#x26;D research and design engineer from, Nordic.</p>
<p>I work on cloud stuff, at the semiconductor company.</p>
<p>
  So my company makes money selling chips that connects your smartphone to your
  headphones, the pencil on this fruity tablet that you have or tracks the medical
  the pharmaceutical products that you need, and, and allow that to happen.
</p>
<p>I work in Norway.</p>
<p>I'm not the only one from Norway.</p>
<p>I hear we have two more speakers from Norway, so I'm pretty proud of that.</p>
<p>
  You can find all my links on
  <a href="https://coderbyheart.com/socials">coderbyheart.com/socials</a>.
</p>
<p>
  The slides, I've shared them on my Mastodon profile in case you want to follow
  the links or whatever, but you can reach me there.
</p>
<p>So first let's recap a bit.</p>
<p>What is push to deploy?</p>
<p>Push to deploy means:</p>
<p>
  when I do a change on the thing that I'm working on and I committed to my
  versioning system, probably Git in most of the cases, and then I push it,
  meaning I push it to the, to the server, make it available to, to everybody in
  the team.
</p>
<p>
  So I push it from my local computer to the source code repository that we use as
  a company.
</p>
<p>
  But then from there, it should trigger the chain, your pipeline, your flow, that
  puts it in front of the real customer.
</p>
<p>
  And it's not the test system or it's not your colleagues, it's actually whatever
  makes you money.
</p>
<p>
  So it can be, if you are software as a service, it could be your website that it
  shows up there, or, if you're shipping, software to other of your collaborators,
  your customers, then it arrives there and they can use it.
</p>
<p>So this is really important fact that we, we want to optimize for this.</p>
<p>
  And this is, when I, when I talk about push-to-deploy, this is what I mean.
  Deploy is not just is it running somewhere, but is it used by the end user?
</p>
<p>And there is a fantastic book, I hope all of you have read it.</p>
<p>
  If not, this is a reminder to read Accelerate because, the first half of this
  book explains why push-to-deploy is so amazing.
</p>
<p>And then the second part is it actually shows the proof how they figured it out.</p>
<p>The scientific background, the methodology they used, is explained.</p>
<p>
  So A, if you want to convince somebody, you can prove that the numbers in there
  are correct, and B, you can apply this methodology in your company as well to
  test and figure out and learn more on how to get code faster into production.
</p>
<p>Push-to-deploy is kind of a mantra for me.</p>
<p>There are so many principles out there that we learn as crafters.</p>
<p>We get some of the very outdated ones.</p>
<p>
  S.O.L.I.D., we get, maybe, I dunno, TDD, is kind of a principle, that we learn
  or inside-out-testing, outside-in-testing, Domain Driven Design, like all of
  these are many concepts that are very good.
</p>
<p>
  But for me, I feel that over my experience over the last years, it's really
  that, focusing on this thing, getting the code out there as fast as possible,
  applies in any domain that I've worked on.
</p>
<p>
  I now work in IoT, but I've worked in FinTech, I've worked in the gaming
  industry, I've worked in in advertising, in marketing.
</p>
<p>So kind of everywhere this would and will still apply.</p>
<p>
  So it's a universal thing, principle, and it's really easy to remember what to
  do, how to get there is the hard part, however.
</p>
<p>So why do I want it?</p>
<p>What does it give me?</p>
<p>It's not because I get surprises from it, it's to deal with surprises.</p>
<p>
  And I think we understand now since like maybe the last five years, this is more
  or less common, is that we live in a volatile world.
</p>
<p>Everybody went through the pandemic.</p>
<p>That was probably the call for the globe that nothing can be predicted anymore.</p>
<p>Until before that we had some financial crisis.</p>
<p>We had 9/11, we had some of these Black Swan events that nobody could predict.</p>
<p>
  And even if you can predict them, then you don't know what effect they will
  have.
</p>
<p>And that makes them really dangerous.</p>
<p>
  But the overall learning is that it's completely futile to do any planning, any
  long-term planning that goes beyond maybe this week we don't know what's gonna
  happen next week.
</p>
<p>You have surprises everywhere.</p>
<p>Like you get surprises if you build a product for your customer.</p>
<p>Your customer last week said, I want this button on the left side.</p>
<p>
  And then they get the new Chief Marketing Officer and they say, no, I want it on
  the right side.
</p>
<p>Here's money. Make it happen. I don't care.</p>
<p>
  So, and you can tell, but last week you told us left side, and now you're
  telling right side, and yes, fine, I'm the customer, I'm paying. Surprise
  change.
</p>
<p>
  And you can, and we heard here, sometimes you need to push back and say, no,
  that's not right.
</p>
<p>But sometimes it's just like that.</p>
<p>They want it, it makes sense. They change their business.</p>
<p>So we need to follow their decisions or they will pick another vendor.</p>
<p>Surprise success is, is great to have, but can come at a surprise.</p>
<p>You build something which is kind of ahhh ... it's gonna be a cool prototype.</p>
<p>Let's see how that works.</p>
<p>
  And then like two weeks later you learn oh, this is the main showcase at the
  trade show that is happening in Barcelona.
</p>
<p>That happened to me, more than once.</p>
<p>
  When nobody tells you oh ... your stuff is gonna be used in that way, but it's
  just this because it works.
</p>
<p>We all got that surprise delays.</p>
<p>I can go on endlessly.</p>
<p>I can't avoid surprises.</p>
<p>
  But I need my team and my solutions that I work on be ready to deal with these
  surprises.
</p>
<p>
  And that's were pushed-to-deploy really enables me to do this. Because, anytime
  I have this cycle,
</p>
<p>
  I make a change, I put it in front of the customer, I put it in front of my
  team, I put it in front of my stakeholders.
</p>
<p>And by the way, over the lifetime of your product, your customer will change.</p>
<p>
  If you start a new project, in the first day, you are your customer because you
  want to see that it runs in, in the cloud and that kind of the bootstrapping
  works and that you have all the pipelines in place, then you are the customer.
</p>
<p>
  Then next week you're showing the product to your product manager and to your
  team.
</p>
<p>Then they are your customers. And then this changes over time.</p>
<p>But the idea is the same.</p>
<p>
  The people that give you money, that give you power that you need to agree on
  what you're doing, that basically grant you the freedom to keep working on this
  project.
</p>
<p>
  These are your customers and you, every time you put it in front of them, you
  get feedback.
</p>
<p>I list some features that I get from these fast feedback cycles.</p>
<p>
  Really if you've been following Thierry a bit he's here on this conference as
  well.
</p>
<p>
  He has really, really elaborate and very good ran about why, feature branches
  are evil.
</p>
<p>
  And if we do push-to-deploy, meaning we all commit to production code and there
  is, at any given moment in time, we can release our software to the wild.
</p>
<p>There are no feature branches.</p>
<p>
  That also means there are no longstanding conflicts where you have a long living
  branch and then you, you kind of don't put it in production, but you put it off
  and then you come back and then you need to merge everything together to get it
  to work.
</p>
<p>Don't do this. It will save you a lot of work.</p>
<p>And, it will also not block others if they depend on your work.</p>
<p>Just finish your work.</p>
<p>"Just" an evil, very evil word.</p>
<p>Just finish your work.</p>
<p>Get it in production then everybody else can depend on what you've done.</p>
<p>
  Of course, that just finish your work means we need to kind of slice our work
  smaller and smaller iterations so we can basically drop whatever you're working
  on.
</p>
<p>
  When I work not on any project, basically on any project, if somebody comes to
  me and says, Markus, I need this.
</p>
<p>
  I am prepared for this because, again, I can't predict if they say, is this
  important?
</p>
<p>
  Of course we assume, let's assume just they have a good point why they want me
  to do this.
</p>
<p>Then I'm able to drop what I'm working on maybe within one, two hours.</p>
<p>
  I don't need it immediate, but I can finish what I'm working on, finish, write
  my test, put it in, push it, release it, maybe put it behind a feature flag, but
  it's out there.
</p>
<p>I can drop it, I can get my mind off it.</p>
<p>I don't need to remind my colleagues, "Hey, I'm still working on this."</p>
<p>"Please wait. Or please rebase your branch against it."</p>
<p>What, whatever. I push it out, it's done.</p>
<p>I can shift my focus.</p>
<p>And with a lot of surprises,</p>
<p>shifting focus happens constantly.</p>
<p>
  There's unfortunately, a big learning in my career is: you cannot avoid these
  surprises.
</p>
<p>I tried it many years ago. It didn't work out.</p>
<p>
  Reiterating it a bit, unfortunately, the surprises also come from many things
  that you don't like.
</p>
<p>There are positive surprises, but very often there are unfortunate surprises.</p>
<p>So we want to be, and we need to be in this constant loop where shit's fucked.</p>
<p>
  Meaning like you have technical depth that you need to change in order to get
  this new feature, or there's a new feature that you don't want to do, but you
  have to do it.
</p>
<p>And then yes, we need to change it.</p>
<p>And sometimes this means we need to completely change our architecture.</p>
<p>
  Alberto said this so good
  <a href="https://vimeopro.com/newcrafts/newcrafts/video/949429828">in his previous talk</a>.
</p>
<p>If you haven't been here, please watch it.</p>
<p>We can only make decisions based on the knowledge we have now.</p>
<p>
  But we cannot limit the future capabilities of our systems based on the past
  knowledge.
</p>
<p>
  We learn new stuff, we learn that there is new value to be created and we need
  to go where we can create this value.
</p>
<p>And that sometimes unfortunately means we need to change everything.</p>
<p>So we need to fuck shit up, so we can find it out.</p>
<p>So we can find it out. This loop.</p>
<p>The faster this loop is, the more we learn, the less painful it becomes.</p>
<p>The pain will never go away.</p>
<p>It will always be painful.</p>
<p>There will always be this phase, "Oh my God, not again."</p>
<p>"It's fucked again." But we can get out of it.</p>
<p>
  So we get this good end of feelings of okay, customer's happy, we changed
  something.
</p>
<p>My colleague is happy. It is on a trade show.</p>
<p>Oh, there's good, PR about it.</p>
<p>My marketing colleagues are happy. Hmm.</p>
<p>That gives me then the motivation.</p>
<p>The next time when somebody comes to me with, "Hey Markus, can we change this?"</p>
<p>I'm like: "I hate you for it, but yes, of course we can."</p>
<p>
  What we need in order to be able to push-to-deploy however, that is where the
  craft comes in.
</p>
<p>The plain process of I push code and it goes online.</p>
<p>Well, that's that there's no magic.</p>
<p>
  Like you pack whatever your artifact is of your source code and put it on a
  server, and then user does a request and hopefully it works.
</p>
<p>And of course we want to eliminate this "hopefully".</p>
<p>We want to be certain that it works.</p>
<p>And the only way to get to this point is to write tests.</p>
<p>There, there is no way to do this manually.</p>
<p>You cannot do manual testing.</p>
<p>
  If you have a high velocity, if you release, change your production multiple
  times a day, there is no chance in the world that you get good manual tests.
</p>
<p>Well, you can pay for it, but it, it doesn't make sense.</p>
<p>It economically doesn't make sense.</p>
<p>So you need automated tests.</p>
<p>
  This will give you the certainty that, okay, the change that I'm now making,
  that's usually where you have a high confidence because this is the, the small
  part in your code where you just work on, you have all the things in mind.
</p>
<p>"Okay? It's touching this database here, this is working."</p>
<p>
  Where you probably can get away with little tests, to make sure it works because
  you also just tested it.
</p>
<p>
  The problem is all the other stuff out there in your system that you completely
  oblivious to that it even exists.
</p>
<p>Maybe it's not like you've never touched it, you've never seen it.</p>
<p>
  If there are no tests there, but you kind of change something here, then this
  will break the system.
</p>
<p>
  So we need good tests over the entire system to make sure that even small
  changes, we don't accidentally break something.
</p>
<p>We also write, of course tests, for ourselves.</p>
<p>
  Even I have to drop something, I would, very often then if I'm not finished with
  the the feature I'm working on, write a failing test or a skipped test that
  says, okay, I want like this thing to work like this, put it in a test, commit
  it, it will be there, it will be marked as skipped.
</p>
<p>
  But the next time I pick it up, I can go back to this test because this test,
  and I design outside-in and this test will tell me: "Hey, this is kind of the
  thing", "that the user wants to do."
</p>
<p>
  And then, okay, alright, I remember, okay, now I know what to kind of implement
  under the hood to get it to work.
</p>
<p>
  And of course, like this is the fight we constantly have as crafters, who
  believe in testing is the people who are not there, yet.
</p>
<p>Who kind of know it's good, but it's too much effort.</p>
<p>This is our job to make it easy for them to follow us.</p>
<p>
  We can't just say: "Oh, let's go there.", "This mountain is not so high as it
  looks like it's kind of, it's fine." "You will be fine." No, we have to go
  ahead. We need to lead with good example. We sometimes even need to make the
  path there.
</p>
<p>Because getting started is really, really hard.</p>
<p>
  The thing at the side here, yes, of course, don't have too many tests.
  Especially when we start with TDD, we start making, building a lot of tests.
</p>
<p>
  And then at some point we have many tests that cover the same feature or are no
  longer really useful.
</p>
<p>Remember to delete your tests.</p>
<p>
  Because this will remove cognitive load from everybody who's coming in later and
  trying to understand what is essentially happening here or have like a giant
  file that tests all the variants here.
</p>
<p>Maybe it's not super useful for them to understand it.</p>
<p>And this is where we come to the vacation part.</p>
<p>I'm now not working, on code.</p>
<p>I'm here on stage and on this conference.</p>
<p>If I have well-tested code, I can even be productive while I'm doing nothing.</p>
<p>So, here's an example from my inbox.</p>
<p>This is like Thursday, Friday, Saturday, Sunday.</p>
<p>
  So while I'm not working, I'm still producing value respectively, not I, but the
  code base that I created is covered in a lot of tests that I can deal with the
  messy state that the JavaScript ecosystem is in, where we get dependency updates
  every hour.
</p>
<p>
  And if you want your code base to not be outdated, you basically need to deal
  with this in an automated way.
</p>
<p>
  And having a well tested code base means if a new dependency changes, a test
  will be run, it will be checked that it works.
</p>
<p>
  And if it works, we bump the dependency and release an new version to
  production. we get automated security updates.
</p>
<p>
  We get new, not new features because we don't use the new features, but maybe we
  get performance improvements.
</p>
<p>
  And the best thing is, if I get back from vacation, I did three weeks vacation
  now, I come back to a code base with basically no new PRs.
</p>
<p>
  Nobody needs to take care of maybe stuff that piles up while I'm away on some of
  my projects where not more people are working on it.
</p>
<p>
  Again this mindset push-to-deploy, means a bot, AI, hello, a bot can create a
  commit.
</p>
<p>It gets automatically verified. Is it good?</p>
<p>And if yes, then fine.</p>
<p>
  I talked about this principle where, I can save the work that I'm working on and
  I save the intended user feature.
</p>
<p>
  That was a hint on, what I think is for me, really the best way to approach
  this, push-to-deploy and the fast feedback cycle is using outside-in TDD.
</p>
<p>Very, very simply explained.</p>
<p>
  If we talk about writing tests we have a calculator and we have a function that
  adds two numbers, or we have a calculator, it's the product, I want a
  calculator.
</p>
<p>
  So I can implement my calculator and the test for it by really starting to
  implement all the features.
</p>
<p>Like I have an add function and I write a test for it.</p>
<p>I have a subtract function, I have a root function.</p>
<p>And then so I build all these small things that a calculator should be doing.</p>
<p>"Oh, let's pull in modulo. What else do I have?"</p>
<p>
  I need to deal with rendering maybe exponential numbers, how to display floats,
  whatever.
</p>
<p>
  Like I can spend a lot of time on testing all the components and then I pull
  these together into bigger components.
</p>
<p>But since everything is covered under here, this goes very fast then.</p>
<p>
  But I can spend a lot of time in the depth of some obscure feature that I might
  be needing to test it.
</p>
<p>
  Where outside-in TDD is really taking from the beginning the perspective of the
  user.
</p>
<p>
  It's not deconstructing the calculator into its small components, but it's
  saying:
</p>
<p>"Hey, as a user, I want to add two numbers.</p>
<p>Then you have the calculator and say, given two numbers, I expect the sum.</p>
<p>You have a user story already and you get clarity from your stakeholders.</p>
<p>Okay, this is what we want.</p>
<p>None of your stakeholders said they want to do modulo.</p>
<p>Nobody says they want fractions.</p>
<p>Maybe you will never need it.</p>
<p>
  So when, when you use outside-in TDD you really focus on the features and you
  only do that.
</p>
<p>
  And this is really, really helpful to, to make sure that you only implement the
  necessary stuff.
</p>
<p>
  However, that means there is, there will be some maybe shortcuts or not like
  super optimizations for any kind of use case that will lead you later to do more
  refactoring.
</p>
<p>But then at least we know that the refactoring is needed.</p>
<p>
  Like in the beginning, I have a calculator which has the add implemented, just
  in the main function.
</p>
<p>
  And if I have 20 different operations I probably want to refactor this in into
  smaller modules, so it's easier to compose, but maybe I never need this.
</p>
<p>
  The ultimate hint for you and what to really look into, I think is, Behavior
  Driven Design, which really takes this to an extreme that it decouples the
  testing, the language the tests are written in, from the actual implementation.
</p>
<p>
  And BDD takes a step that it moves your basically description of what the thing
  does.
</p>
<p>
  There is a user who has a calculator and then they input two numbers and they
  get a sum out of it.
</p>
<p>They put this in like written plain English text.</p>
<p>
  And now when you have a plain English text, you need something that runs this
  against your system.
</p>
<p>
  But the cool part is that regardless of what you do here, you can change
  everything in your implementation.
</p>
<p>
  You can change the language, you can change from Python to JavaScript. You can
  change from Java to whatever this file here with your test.
</p>
<p>That will not change.</p>
<p>
  And there's a really beauty in it that it preserves your intended behavior of
  your system in a state that is not affected by any refactoring.
</p>
<p>
  And I've seen this so often that when you start refactoring and removing stuff,
  you start uncommenting tests and maybe delete them, maybe move them around.
</p>
<p>You forget to turn them on or add them back.</p>
<p>And that can happen. Where if you have a BDD feature, it will not.</p>
<p>
  So that is really an important, way to capture the behavior of a system in a way
  that's also understood by all stakeholders.
</p>
<p>
  I know that's a myth that with BDD, I can go to my product manager, and say:
  "Hey, let's write a BDD scenario together."
</p>
<p>That's not true.</p>
<p>
  I don't think many people have managed this, but at least like they can, if I
  give it to them and say: "Hey, this is what my system does," "it takes two
  numbers and gives out a sum" they can understand it.
</p>
<p>They don't get scared by some syntax, by curly braces.</p>
<p>And they are willing to read this.</p>
<p>
  And also new people who come in a team, they can start browsing through that
  without needing to dig through your source code system to figure out what's
  actually happening in which unit test is relevant.
</p>
<p>I can talk forever about BDD.</p>
<p>
  It's also amazing because it groups together features or or implementation into
  things that a user would do.
</p>
<p>
  A good example is that, you very often see, you have a, a storage class, a
  repository implementation that can store stuff.
</p>
<p>
  And often it's really stuff because it's generic, it's not a book storage
  because you don't need that.
</p>
<p>Technically, a database can store anything.</p>
<p>So your database repository adapter will also store anything.</p>
<p>
  And that's not helpful. Like the user is not using a repository, the user is
  using your website.
</p>
<p>And then they have a list where they can add features.</p>
<p>
  So, and if you have a feature that connects this part of your source code to
  this feature, you know exactly when and where it's used.
</p>
<p>
  That was just a glimpse of where like this explodes in complexity of how to
  test, why to test, what to use, what technology to use, which approaches to use.
</p>
<p>And, the unfortunate truth is that, it is extremely hard to get started.</p>
<p>
  The effort it takes, like when there are no examples in your source code base,
  is insane.
</p>
<p>And it's super frustrating.</p>
<p>And then over time, the more examples you have, it gets easier.</p>
<p>
  I'd say that at some point, like if you have too many examples, it might even go
  up, because then your test suit gets too long, the runtime gets too long,
  whatever.
</p>
<p>But that's unfortunately the truth.</p>
<p>
  And that's what I said before, like, we need to be good at giving examples and
  be a good example.
</p>
<p>So how do we get there? How do we become good?</p>
<p>
  I know I want to test and I was in, in many times in a situation where I came
  back from a conference, heard about this testing is cool, and then at work
  nobody writes tests.
</p>
<p>And this has worked for me in my career very well.</p>
<p>
  The 20% rule, which basically means if I have a new project or I have an
  existing project, but I want to learn something new where this could work.
</p>
<p>
  So for instance, I want to try out event sourcing. I know I have kind of, we
  have never done this before to, to make events the main source of truth for our
  system.
</p>
<p>We just use MySQL and do updates.</p>
<p>That's fine, but I really want to try it.</p>
<p>So I would then risk it.</p>
<p>That's the truth.</p>
<p>
  Like 20% means of the deliverable, like using event sourcing is maybe 20% of the
  features.
</p>
<p>
  And if I figure out at some point "okay, it won't work" "it is the wrong
  decision" I still can go back and rip it out and use the old approach.
</p>
<p>
  You can do this for everything you have 20% of your time you can always carve it
  out because you have, after some point of time, you have enough knowledge on how
  to build a system, and very often you repeat stuff.
</p>
<p>
  So try to introduce some experiment even in your own code, you might learn with
  it.
</p>
<p>And if it fails, well, you take a weekend and implement it the old way.</p>
<p>And this can even work as a team.</p>
<p>
  You need a commitment to that, but there is room for it. the next thing is and
  this is a thing that worked well for me, but the gist in here is that we become
  good by learning not everything.
</p>
<p>We need to find the things that work for you.</p>
<p>And that's, that's okay. You can't know anything.</p>
<p>
  It's just too much. But learn some of the techniques that serve you well, and go
  deep on them because the truth is there is not one thing how to implement
  anything.
</p>
<p>There is always another way to do it.</p>
<p>And so the same with testing.</p>
<p>I said outside-in is great.</p>
<p>
  There are enough people and example that say no inside-out is the only way to
  go.
</p>
<p>And we both are right. We get good systems.</p>
<p>It's just for some of you, it the one way works the other way maybe doesn't.</p>
<p>I talked about end-to-end testing.</p>
<p>
  Push-to-deploy also depends on one of these ideas here. Reproducible builds. You
  need to learn how to package software in a way that you can kind of repeat it,
  repetitive, deploy it.
</p>
<p>If you learn this by the way, you go into scalability.</p>
<p>I put RegEx on here because for me, RegEx allows easy refactoring.</p>
<p>I can using RegEx, do a lot of refactoring.</p>
<p>I personally, I don't use refactoring tools in IDEs.</p>
<p>I too often change IDEs.</p>
<p>I was forced to use Vim when I was in my early companies.</p>
<p>So I came from a path where I didn't have IDEs in the beginning.</p>
<p>
  So I know how to clone hundreds of repositories and do changes across many
  repositories at the same time using RegEx because I also am not afraid of code
  duplication.
</p>
<p>Another thing here, code duplication is not a bad thing.</p>
<p>It allows you to quickly iterate.</p>
<p>
  I don't need to make code reusable so I can apply it with all the like,
  perfectly designed APIs in multiple projects.
</p>
<p>There's no value in it.</p>
<p>
  It only becomes a problem if you have bigger code bases that reuse the same
  piece of code that has the same bugs, but then well, you still can use RegEx to
  find it and fix it.
</p>
<p>This is where craft really comes in.</p>
<p>
  One of the ideas is that we have is that, we try to understand the tools that
  exist, keep an open mind, that's really important.
</p>
<p>
  What is out there? Have a look at it, do these experiments, but really find the
  tools that work well for you because only then also can you explain it to your
  team?
</p>
<p>Why are you doing this? Why are we doing code duplication here and why not here?</p>
<p>
  How can I deal with tests when I can't run the system, on my computer and to end
  testing?
</p>
<p>
  Awesome. So this is, we need to have a discussion with our younger colleagues on
  really not on a level "This is how I do it.", "So you have to do it!" but I need
  to enable them to understand why we're doing this also because they might find a
  better way and then can tell me, Hey, there's a new way to do it.
</p>
<p>
  One thing I would really like to reinforce is that, testing as in test driven
  development, is a universal skill.
</p>
<p>Meaning if you invest in this, this will be useful for your entire career.</p>
<p>Like it works in, in all languages.</p>
<p>It works in embedded, it works in high level languages.</p>
<p>There's no programming language that can work without tests.</p>
<p>
  So all of them can work without tests, but there is no language that is perfect
  because tests are not for the language.
</p>
<p>Tests are for your assumption.</p>
<p>Tests encode your business knowledge and the requirements that you have.</p>
<p>
  Yes, you might write some tests that your data is stored correctly in the
  database or the integration against an API works, but mainly tests serve as a
  documentation.
</p>
<p>That's why I put this blue reminder here.</p>
<p>Don't test your language features.</p>
<p>
  If you have a feature that calls some functions and you have, for example,
  TypeScript, like I see this very often in, in lambda functions like a Lambda
  function pulls in some collaborators and then passes in an event to another
  collaborator.
</p>
<p>
  I don't write tests for lambda functions, which they are very kind of your, the
  C and MVC and model view controller.
</p>
<p>I don't write tests for the controller.</p>
<p>
  If I have TypeScript, which then ensures that I can call my collaborators with
  the right parameters and everything typed, the input is validated.
</p>
<p>So there's no point in like doing language tests.</p>
<p>We can only do this fast iteration if we start owning our own infrastructure.</p>
<p>
  I've never seen it work well with a DevOps or an ops team, which you hand over
  stuff over the big wall and say: "Hey, please run this for me."
</p>
<p>
  And then you just ship them new features and they are responsible for running
  it.
</p>
<p>They're responsible for performance, for migration, for whatever you need.</p>
<p>Unfortunately, I think that's the truth.</p>
<p>You need to learn more about how to run the things that you're building.</p>
<p>
  And the way to do it is, of course, going in the cloud, going serverless, or
  learning about, deploying infrastructure, using Kubernetes or whatever works for
  you.
</p>
<p>For me, serverless worked very well.</p>
<p>
  But one of the like big, big important decisive, books for me to learn and start
  learning more about it was, REST in practice.
</p>
<p>That's the top one. It is really a fantastic book.</p>
<p>
  The main idea is like, how can we build systems that work when everybody failed
  to build these systems?
</p>
<p>
  The only system we know that really works well for billions of people for the
  last 35 years is the internet.
</p>
<p>
  And the internet is built on very simple principles. HTTP requests, status codes
  and systems are broken all of the time.
</p>
<p>
  So if you keep that in mind, if that's your mode of, how you design your system,
  everything fails all the time.
</p>
<p>
  How to deal with that. This book tells you how to use that, as a way to design
  your architecture, and it leads you natively towards a microservice
  architecture.
</p>
<p>And you can use this and apply this many different contexts.</p>
<p>
  And if you then go a bit more into the serverless route, then definitely start
  reading. the new book that just came out a few months ago, serverless
  Development in AWS fantastic introduction.
</p>
<p>I'm such a big fan of serverless also because I work a lot in IoT.</p>
<p>
  In IoT we have this a problem that is made for serverless where we have millions
  of devices which connect to your server.
</p>
<p>You can't predict it.</p>
<p>It needs to scale.</p>
<p>You can't have one machine that deals with these requests.</p>
<p>
  There's a long talk, by me about why this is such a great match. many systems
  have properties where serverless is really a good application.
</p>
<p>You should invest a bit of time into it.</p>
<p>
  Now we are close to the end of my time, so I want to finish with what I said in
  the beginning.
</p>
<p>
  You need the support system outside of work because very often you're alone in a
  company, and you're the only one who's pushing this forward.
</p>
<p>
  My journey basically as a crafter was really, really, really supported by the
  outside support, not within my company,
</p>
<p>but it's really, there's a big community out there that can help you.</p>
<p>
  And by the way, no, there are no certificates, like softwarecrafter
  certification is a scam.
</p>
<p>But you can go to my website and get a certificate.</p>
<p>I give them out for free if you want it, but it's a scam.</p>
<p>So the solution, of course is you are already here.</p>
<p>So you did the first step.</p>
<p>You went to, to a conference, to connect with others.</p>
<p>And this is the, I think the only way that really worked.</p>
<p>
  And if you start digging a bit into the software crafters community, then you
  will find there's many resources.
</p>
<p>Advocate internally, if you're at the point and you need to grow the others,</p>
<p>
  I said that before, you need to be an advocate, a good example, but the
  inspiration for that, and many have the same problem, that you want to push a
  topic and you don't know how is, to go to external communities.
</p>
<p>
  We have a nice website, software craft communities worldwide,
  <a href="https://softwarecrafters.org/">softwarecrafters.org</a>, where we list communities
  all around the world.
</p>
<p>NewCraft is part of this network.</p>
<p>
  There are other conferences, like I really love the Friends of Good Software
  conference, was from a testcraftCamp.nl
</p>
<p>
  Now it's, it's more on, software quality, really great communitie, it is
  virtual, you can participate here virtually.
</p>
<p>This is really cool. Organize your own, I organize a few conferences.</p>
<p>
  I organized Codefreeze in Northern Finland, come there, 80 people, a lot of
  snow, Aurora, Sauna, and maybe a bit software craft talks.
</p>
<p>
  One thing that really has helped me, and this no longer exists, but I just put
  it up here as an example
</p>
<p>I also created my own peer groups.</p>
<p>
  I think, some people call these mastermind circles where you just reach out to
  people from other organizations.
</p>
<p>And for me, this was a time where I was a CTO in startups in Frankfurt.</p>
<p>That's why #TechLeadsFFM, that's the region.</p>
<p>
  And I reached out to other CTOs, from startups, and, we started creating this
  circle, where meeting as a group, like 15 people.
</p>
<p>And then we would have one-on-ones.</p>
<p>And everybody was really thankful that this existed.</p>
<p>It takes really little effort.</p>
<p>
  So reaching out to a few people, finding a coworking space where you can hang
  out with a few beers and do this once every two months.
</p>
<p>
  But the important part is that you kind of connect with others and start sharing
  your things and the problems that you have, because everybody has these
  problems.
</p>
<p>And of course, what I'm doing now is also part of getting better at my craft.</p>
<p>
  Because when I stand in front of people, this trains your skill to voice what's
  important for you.
</p>
<p>And I really invite all of you.</p>
<p>
  I don't want to be on this stage, don't want to see more people like me, white
  English speaking dudes from Norway, or from Europe.
</p>
<p>I want to see more diversity on conferences.</p>
<p>And unfortunately, it's a giant problem.</p>
<p>So please take the courage and apply.</p>
<p>And the first thing you need</p>
<p>to do is just write an abstract.</p>
<p>You don't need a presentation.</p>
<p>You just need four sentences and three takeaways, and that's it.</p>
<p>Submit it to a conference like NewCrafts.</p>
<p>And if they take you, then you have to make the presentation.</p>
<p>I made a video about this, like how simple it is.</p>
<p>And if you want to write an abstract, I will help you.</p>
<p>We need to get more.</p>
<p>
  I've been doing this too long, so there need to be more people who are doing
  this.
</p>
<p>Not so long as I am.</p>
<p>
  But it's a way, of course, I get to go to the speaker's room, I get to meet
  amazing other speakers.
</p>
<p>I can just chat to them and learn about stuff.</p>
<p>And get the insight that will, again, help me improve my skills.</p>
<p>It's, it's really invaluable.</p>
<p>So I will be closing with</p>
<p>What's the point of this?</p>
<p>I talked about push-to-deploy, we are learning, we need this skill.</p>
<p>And I'd like to reiterate why do we want to be good at what we are doing?</p>
<p>
  And basically, Alberto said it before in the talk, we can't just put shit in
  front of our customers.
</p>
<p>Everything we do needs to be very good.</p>
<p>If it's a good idea, shitty executed.</p>
<p>
  If it's the most amazing screen design, but the response time is 15 seconds
  before the page loads, nobody's gotta see this amazing idea.
</p>
<p>They will be gone.</p>
<p>The craft itself it's not an end to itself.</p>
<p>
  We support business ideas with our craft and through us, we are the conduits
  that take the business idea.
</p>
<p>
  And, I might not agree with all of them, but I don't have the data to say to
  them: "Hey, this idea is really shitty."
</p>
<p>
  I need to treat any idea that's coming to me fairly, give it my best to put it
  in front of the user and let the user decide if this idea is shitty, because
  they are paying my salary, not I.
</p>
<p>
  So that really is why we need to be good at what we are doing to give any idea
  that is out there, a good chance to survive and maybe to be the surprise success
  we have been preparing for.
</p>
<p>Thank you very much.</p>
]]></content:encoded>
		</item>
		<item>
			<title>Serverless Development on AWS</title>
			<link>https://coderbyheart.com/serverless-development-on-aws-review</link>
			<guid isPermaLink="true">https://coderbyheart.com/serverless-development-on-aws-review</guid>
			<pubDate>Fri, 10 May 2024 16:45:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>Great book for devs &amp; managers: serverless intro &amp; potential!</description>
			<content:encoded><![CDATA[<!-- ![Serverless Development on AWS](../media/serverless-development-on-aws.jpg) -->
<p>
  I can fully recommend the book for developers who are starting to work more with
  serverless but as well for technical managers who want a better understanding of
  the potential of the serverless paradigm and the mindset shift needed to tap its
  full potential. It’s a must read for every developer that works with AWS because
  more and more powerful services on AWS are built from the ground up as
  serverless offering. Making the most of them will be a skill that takes time to
  master, and this book will provide you with many well explained shortcuts.
</p>
<p>
  For a more seasoned serverless engineer the book is still valuable, especially
  since you want it in your bookshelf to give to colleagues. Especially chapter 2
  (“Enterprise Readiness for Serverless”) is a great resource that discusses the
  value of serverless as an innovation and value creation enabler which is
  supported by a great interview with
  <a href="https://theserverlessedge.com/meet-the-team-behind-the-serverless-edge/">David Anderson</a>.
</p>
<p>
  This chapter also discusses the serverless mindset, which is a distinctly
  different approach to architecting solutions compared to instance-based
  architectures. Adopting this mindset is both hard to achieve and complicated, so
  this chapter does a great job of highlighting the different aspects that
  serverless solution designers need to conquer.
</p>
<p>
  The highlight of the book is certainly the third one (“Software Architecture for
  building serverless microservices”) which does a very good job of explaining how
  to design efficient serverless architectures and the important role events are
  playing. If you are familiar with event-based systems this might not surprise
  you: after all, serverless works well if all services are loosely coupled and
  all efficient architectures are designed with this in mind. This is a good
  reminder to check out my reading guide of
  <a href="https://coderbyheart.com/rest-in-practice-reading-guide">REST in Practice</a>,
  which still applies to these kinds of architectures.
</p>
<p>
  The implementation of these architectural concepts is then described in chapter
  five (“Serverless implementation patterns”) and I really liked that they
  invested a good portion of it to describe strategies on how to deal with
  third-party services … because the reality is that not everything runs smoothly
  within AWS. In my day to day job, nearly every feature deals with some kind of
  third party API. Dealing with those APIs failing gracefully is an important
  aspect of any implementation. The chapter provides good examples of how to
  actually solve this.
</p>
<p>
  In this and the following chapter seven (“Implementing serverless applications”)
  we find good tips on structuring and deploying applications and how to use the
  serverless platform as an event flow engine so the code you write can focus on
  implementing the business domain logic.
</p>
<blockquote>
  <p>
    It’s an important learning that utilizing flow control features like EventBus
    and StepFunctions will yield better observability and unlock resilience
    features like automated retries that otherwise would need to be implemented in
    software.
  </p>
</blockquote>
<p>
  Of course, chapter 7 (“Testing serverless applications”) was a must read for me,
  given that
  <a href="https://coderbyheart.com/it-does-not-run-on-my-machine">I have spoken extensively about my approach</a>
  on conferences. The chapter explains very well the compromises that have to be
  made when testing a serverless architecture. The promoted approach is: unit-test
  your business logic implementation, and add end-to-end tests for the
  business-critical paths. This way you ensure that the implementation is correct
  and the solution once deployed does what it is supposed to do. This aligns very
  well with my experience, so it was nice to read in the chapter’s interview with
  <a href="https://www.linkedin.com/in/hamilton-sarah/">Sarah Hamilton</a> how an expert
  approaches this topic.
</p>
<p>
  Don’t skip chapter 8 (“Operating serverless”) because it provides a good
  introduction to monitoring serverless solutions and shows that it’s quite easy
  to get started with building monitoring for your solutions.
</p>
<p>
  Overall I really liked the interviews that conclude every chapter, because they
  add more flesh to the sometimes very technical concepts discussed in the
  chapters and help to support what was discussed in the preceding chapters.
</p>
<p>
  You can buy “Serverless Development on AWS”
  <a href="https://amzn.to/4drVBjg">on Amazon</a>.
</p>
]]></content:encoded>
		</item>
		<item>
			<title>How to develop a cellular IoT project</title>
			<link>https://coderbyheart.com/how-to-develop-a-cellular-iot-project</link>
			<guid isPermaLink="true">https://coderbyheart.com/how-to-develop-a-cellular-iot-project</guid>
			<pubDate>Thu, 08 Feb 2024 14:00:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>A small lecture I gave on how to approach designing the software architecture for a cellular IoT project.</description>
			<content:encoded><![CDATA[
<p>
  This is a small lecture I gave to a team of students on how to approach
  designing the software architecture for a cellular IoT project.
</p>
<p>
  It uses the <a href="https://www.nordicsemi.com/products/nrf9160">Nordic nRF9160</a> as a
  habit tracker in a 12 sided enclosure and connects to
  <a href="https://aws.amazon.com/iot/">AWS IoT</a>.
</p>
<p>In the talk I speak about:</p>
<ul>
  <li>design considerations</li>
  <li>how to separate work between three teams</li>
  <li>make sure the project is aligned with the user's goals</li>
  <li>how end-to-end tests support integration between teams</li>
</ul>
<p><a href="https://www.youtube.com/embed/4dxdToouAI8">https://www.youtube.com/embed/4dxdToouAI8</a></p>
]]></content:encoded>
		</item>
		<item>
			<title>How i use @internetofshit as a reminder to build safe and usable IoT products</title>
			<link>https://coderbyheart.com/use-internetofshit-as-a-reminder-to-build-safe-and-usable-IoT-products</link>
			<guid isPermaLink="true">https://coderbyheart.com/use-internetofshit-as-a-reminder-to-build-safe-and-usable-IoT-products</guid>
			<pubDate>Tue, 19 Sep 2023 13:30:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>an ad-hoc tech talk about an inspirational Twitter account</description>
			<content:encoded><![CDATA[
<p>
  Today I did an ad-hoc tech talk for my team where I talk about what the Twitter
  account [at]internetofshit can teach us cloud engineers about what to consider
  when it comes to designing solutions for the internet of things:
</p>
<p><a href="https://www.youtube.com/embed/FlTWph4At6o">https://www.youtube.com/embed/FlTWph4At6o</a></p>
]]></content:encoded>
		</item>
		<item>
			<title>Introducing teamstatus.space</title>
			<link>https://coderbyheart.com/introducing-teamstatus.space</link>
			<guid isPermaLink="true">https://coderbyheart.com/introducing-teamstatus.space</guid>
			<pubDate>Tue, 05 Sep 2023 22:30:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>Use teamstatus.space to collect project status updates asynchronously and distributed to speed up your sync meetings</description>
			<content:encoded><![CDATA[
<blockquote>
  <p>
    collect project status updates asynchronously and distributed to speed up your
    sync meetings
  </p>
</blockquote>
<p>
  I am happy to announce a side-project that I’ve been working on for a while, and
  which I have been using daily since its inception.
</p>
<p>
  Use teamstatus.space to track what you are working on, as it happens. It may be
  small updates, or big releases.
</p>
<p>Status updates are associated with projects, which belong to organizations.</p>
<p>
  You can invite coworkers to the projects you are working on. They then can also
  add their own status to the project.
</p>
<p>
  Team members can see status, and add reactions. They are an important tool to
  share praise 🌟, but also prepare the next sync meeting. Status can be marked as
  significant 🚀, so they will be highlighted in the next sync. Or a status can be
  marked as a question 🙋 so they can be discussed during the sync.
</p>
<p>
  And when it's time for a sync meeting with your collaborators, you create a new
  sync which collects all the status updates from the projects your meeting is
  about, from all the collaborators and a time frame you can specify.
</p>
<p>
  This way you do not have to remember all the big and small things that happened
  since your last sync with this team; or the other group of stakeholders you only
  meet every other week.
</p>
<p>
  And since you can share what happened in advance, you can spend your time during
  the meeting to discuss the highlights and the open question which
  <a href="/speed-up-your-meetings-with-pre-written-meeting-minutes">ensures that everyone's time is used most efficiently</a>.
</p>
<p>
  This project is in an early stage and I am interested to hear your feedback,
  especially whether this is a tool you would use yourself. As a thank you for
  your input you’ll be able to keep using the tool without charge with all paid
  features, if and when this turns into a paid service.
</p>
<p>
  I’ve created a self-guided onboarding flow which will guide you through the
  features, so just head over to <a href="https://teamstatus.space">https://teamstatus.space</a> to sign up. I’m happy
  to receive your feedback on a channel that you prefer, but I want to dog-food
  it, so please use the <code>$teamstatus#feedback</code> project for your feedback.
</p>
]]></content:encoded>
		</item>
		<item>
			<title>Exploring better ways to write tests</title>
			<link>https://coderbyheart.com/exploring-better-ways-to-write-tests</link>
			<guid isPermaLink="true">https://coderbyheart.com/exploring-better-ways-to-write-tests</guid>
			<pubDate>Fri, 09 Jun 2023 13:30:00 GMT</pubDate>
			<dc:creator>Markus Tacker</dc:creator>
			<description>My approach to testing embedded firmware using real hardware.</description>
			<content:encoded><![CDATA[
<p>
  <aside class="hero"><picture style="aspect-ratio: 1300 / 867" data-cdn><source srcset="https://7w7z6ydf2htamqdsm6nbxm7sma0nkltc.lambda-url.eu-central-1.on.aws/coderbyheart.com/media/1b0d394a2508101b85fd701bb7625c88578bdb328ba2cc323c92ecb59c3a325e"><img src="data:image/webp;base64,UklGRsgBAABXRUJQVlA4ILwBAADQCwCdASpAACsAPzmKuFSvKaUjKqz54CcJbACvRcDZPl/yAhXjTVd6ABuLiZrHXa2F89v/3+fUEAYtj82pe7Ah6L19ZbwOmvVXJnBE4zKNxMBiGqWDmQGzPUh/pHzkeD+bb14GjL8IAP7o2LVSjS5A4AsaiwANW/JQ0Cr/M0X3aUczPCP7GKbgxAMXuUdP71k+6aIcoHIBqcJlXBHSHJBALuZbzrFo9GQhq3ZJUPj6JsL1ckFibeQ7oCMxxvT5yEh4cnzpVYrcJwZZasUJLFN9Zwbc3xB1D/Us0lF6AhvqWB+87rYEvLO/AIJynXkGf1mQutAwVH46RGaEwhH1t34OXb0X1TWGtbskxOGgxpPyeGXsieh3/FhnqRlGCNTCcUmAVbGsuc456GNjzeu9brYxEH1Yom1MYZ3u3vAmVKmfH+fVox4OxlA13lb3W6qQ2tVRJgkg0shVUt+K9F8vE6mW5emrEO52Gkc+Vo5LFag9d++cla5m/y4oefWaP2esc4OrAGWbvbpzSlotZ93A6Im1JsRcpE/b9hieR4cq9qoGLrBSBLbDQ4+JbgEk9E0kz3fdGyZfc1a18TiAAAA=" alt="Nordic Testing Days 2023" width="1300" height="867"></picture></aside>
</p>
<p>
  In my talk <em>Exploring better ways to write tests</em> that I gave at this year's
  <a href="https://nordictestingdays.eu/">Nordic Testing Days</a> 2023 in Tallinn, I
  discussed my journey from feeling dissatisfied with testing tools to creating my
  own test runner. My goal of this talk was to inspire others to explore and
  customize their tools rather than settling for the default options available.
</p>
<p>
  For quite some time I have been working on cloud-based software system for IoT
  devices. Initially, I used Gherkin for writing tests, but I encountered
  limitations in expressing complex scenarios, dependencies, and handling eventual
  consistency in cloud-based services. Let me explain how I began using Gherkin
  and describe its format and keywords (given, when, then).
</p>
<p>
  Facing challenges with Gherkin for my specific needs, I ran into issues with
  expressing dependencies between features, directly embedding JSON in feature
  files, managing eventual consistency problems, and dealing with
  context-dependent scenarios. I also realized that I needed better documentation
  beyond just tests. To address these issues, I decided to build my own custom
  test runner, a process that took me three years to develop. The key improvement
  was adopting Markdown for better formatting, hierarchical structure, code
  blocks, and comments. I introduced the "soon" keyword to handle eventual
  consistency in tests. Additionally, I utilized GitHub actions to summarize test
  results in a more user-friendly format.
</p>
<p>
  I also talked about the struggles I faced with architecture diagrams and their
  maintenance. Auto-generated diagrams fell short, prompting me to explore the
  concept of using test traces and observability to dynamically generate
  architecture diagrams based on test runs. This approach would allow for more
  focused and informative diagrams linked directly to actual usage.
</p>
<p>
  In conclusion, I encouraged others to create tools tailored to their own needs.
  I believe that developing personalized solutions can significantly enhance
  efficiency and understanding in both testing and development workflows.
</p>
<p><a href="https://www.youtube.com/embed/dZX7SbGOGn4">https://www.youtube.com/embed/dZX7SbGOGn4</a></p>
<p>
  The slides are available at
  <a href="https://bit.ly/better-test-tools">bit.ly/better-test-tools</a>.
</p>
<p>
  You can read up about my impression under the hashtag
  <a href="https://chaos.social/@coderbyheart/tagged/ntd2023">#NTD2023</a>.
</p>
<p>
  <aside class="hero"><picture style="aspect-ratio: 4976 / 3317" data-cdn><source srcset="https://7w7z6ydf2htamqdsm6nbxm7sma0nkltc.lambda-url.eu-central-1.on.aws/coderbyheart.com/media/21393e3379188735467c02a5c0f72fc8a1e0a62d45373a171e7b730b454a7f47"><img src="data:image/webp;base64,UklGRkIBAABXRUJQVlA4IDYBAACQCACdASpAACsAPzmQwFkvKSajqBVbieAnCWYAudITXGAnw9Dwe7F/CQAXdmU1pEQ54B24TNtqPj4xzpm3Tpc6c+OPRTlf+tJ8ncqKEAD+6Ngn3Ch+b6unP+LSytsbBelmaTfgs/nD3hY3+Pd8hsdrlXNx/ifHCReGRp/KtiioGx7vcQElhtNMgecnmZ7zBFm/2hturCpZhpC+9v1r2UL9n34MckHUoP5Qn4RjlJYmSnUasertee7yK/bLfu4USf1w2c4rrq30iezglvnX7BxGGrCXb+9bAorCMU4M+hIJib7T0LP9I+ZFcMtz2VWMkOVsPnTwRkxe2nGei26vscAnlAyuOW4+xS5NDaM6OEcrkVt+WGKEYoNkQgozuuo20Rj0r+03NA0qIx8MgGJ6FEIY0C6JCAAA" alt="Nordic Testing Days 2023" width="4976" height="3317"></picture></aside>
</p>
]]></content:encoded>
		</item>
	</channel>
</rss>
