<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Tatted Dev</title>
    <description>The latest articles on Forem by Tatted Dev (@tatted_dev).</description>
    <link>https://forem.com/tatted_dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3518366%2Faa49110a-cf6b-4606-a2d8-56a044b78834.png</url>
      <title>Forem: Tatted Dev</title>
      <link>https://forem.com/tatted_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tatted_dev"/>
    <language>en</language>
    <item>
      <title>🍯 Announcing HoneyDrunk.Data: A Multi-Tenant Persistence Layer for Distributed .NET Applications</title>
      <dc:creator>Tatted Dev</dc:creator>
      <pubDate>Tue, 06 Jan 2026 23:34:07 +0000</pubDate>
      <link>https://forem.com/tatted_dev/announcing-honeydrunkdata-a-multi-tenant-persistence-layer-for-distributed-net-applications-4mp5</link>
      <guid>https://forem.com/tatted_dev/announcing-honeydrunkdata-a-multi-tenant-persistence-layer-for-distributed-net-applications-4mp5</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; We just released HoneyDrunk.Data, a provider-agnostic persistence layer built for multi-tenant, distributed .NET 10 applications. It features automatic correlation tracking in SQL queries, tenant-aware repositories, and seamless integration with OpenTelemetry—all without coupling your domain logic to Entity Framework.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem We're Solving
&lt;/h2&gt;

&lt;p&gt;Building multi-tenant applications with proper observability is harder than it should be. You end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scattered tenant checks&lt;/strong&gt; throughout your codebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No correlation&lt;/strong&gt; between your distributed traces and database queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tight coupling&lt;/strong&gt; between your domain logic and EF Core&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test infrastructure&lt;/strong&gt; that requires spinning up real databases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built HoneyDrunk.Data to solve these problems as part of &lt;a href="https://github.com/HoneyDrunkStudios" rel="noopener noreferrer"&gt;HoneyDrunk.OS&lt;/a&gt;—our distributed application framework for .NET.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture: Layers That Actually Make Sense
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│   HoneyDrunk.Data.Abstractions      │  ← Zero EF Core dependencies
│   (IRepository, IUnitOfWork, etc.)  │
└─────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────┐
│        HoneyDrunk.Data              │  ← Kernel integration, no EF Core
│   (Tenant accessor, telemetry)      │
└─────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────┐
│  HoneyDrunk.Data.EntityFramework    │  ← EF Core implementation
│   (EfRepository, EfUnitOfWork)      │
└─────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────┐
│    HoneyDrunk.Data.SqlServer        │  ← SQL Server specifics
│   (Retry, Azure SQL, datetime2)     │
└─────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Your domain projects reference only &lt;code&gt;HoneyDrunk.Data.Abstractions&lt;/code&gt;. No EF Core leaking into your business logic. Swap providers without touching domain code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature Highlight: Correlation Tracking in SQL
&lt;/h2&gt;

&lt;p&gt;This is my favorite feature. Every SQL command is automatically tagged with the Grid correlation ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* correlation:01JXY7ABC123DEF456 */&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;TenantId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;p0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find the exact query from a slow API response&lt;/li&gt;
&lt;li&gt;Correlate database logs with distributed traces&lt;/li&gt;
&lt;li&gt;Debug production issues without guessing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation uses EF Core's &lt;code&gt;DbCommandInterceptor&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CorrelationCommandInterceptor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DbCommandInterceptor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IDataDiagnosticsContext&lt;/span&gt; &lt;span class="n"&gt;_diagnosticsContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;InterceptionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;NonQueryExecuting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;DbCommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CommandEventData&lt;/span&gt; &lt;span class="n"&gt;eventData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;InterceptionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;AddCorrelationComment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NonQueryExecuting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eventData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AddCorrelationComment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DbCommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;correlationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_diagnosticsContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CorrelationId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// SQL comments don't affect query plan caching&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"/* correlation:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; */\n&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use SQL comments specifically because they don't affect query plan caching—your cached plans stay cached.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Tenancy: From HTTP Header to Database Query
&lt;/h2&gt;

&lt;p&gt;Tenant context flows automatically from the HTTP request through to database queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request                 Kernel                    Data Layer
     │                         │                          │
X-Tenant-Id: acme  →  IOperationContext.TenantId  →  ITenantAccessor
     │                         │                          │
                                                   TenantId.FromString("acme")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;KernelTenantAccessor&lt;/code&gt; bridges our Kernel context system with the data layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KernelTenantAccessor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ITenantAccessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IOperationContextAccessor&lt;/span&gt; &lt;span class="n"&gt;_contextAccessor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TenantId&lt;/span&gt; &lt;span class="nf"&gt;GetCurrentTenantId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_contextAccessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentContext&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;TenantId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; 
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TenantId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your DbContext can then apply global query filters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnModelCreating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelBuilder&lt;/span&gt; &lt;span class="n"&gt;modelBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Every query automatically filters by tenant&lt;/span&gt;
    &lt;span class="n"&gt;modelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasQueryFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TenantId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CurrentTenantId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more "did we remember to filter by tenant?" code reviews.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Repository Pattern, Done Right
&lt;/h2&gt;

&lt;p&gt;I know, I know—"repository pattern is an anti-pattern with EF Core." Hear me out.&lt;/p&gt;

&lt;p&gt;Our repositories aren't about abstracting EF Core. They're about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Separating read from write concerns&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Providing a testable boundary&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enabling future provider swaps&lt;/strong&gt; (Dapper for hot paths, anyone?)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IReadOnlyRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;FindByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Expression&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExistsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Expression&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Unit of Work coordinates changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CheckoutAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Cart&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_unitOfWork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_unitOfWork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InventoryItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProductId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Quantity&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Atomic save across all repositories&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_unitOfWork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing Without Docker: SQLite In-Memory
&lt;/h2&gt;

&lt;p&gt;Spinning up SQL Server containers for every test run is slow. We provide SQLite-based test infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderRepositoryTests&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAsyncDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;SqliteTestDbContextFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderRepositoryTests&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_factory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SqliteTestDbContextFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;TestDoubles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateTenantAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test-tenant"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;TestDoubles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDiagnosticsContext&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;

        &lt;span class="n"&gt;_context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;AddAsync_ShouldPersistOrder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EfRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;99.99m&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt; &lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test doubles included&lt;/strong&gt; for tenant and diagnostics contexts—no mocking frameworks required.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Server Specifics: datetime2 and Retry Logic
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;HoneyDrunk.Data.SqlServer&lt;/code&gt; package handles SQL Server-specific concerns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHoneyDrunkDataSqlServer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnableRetryOnFailure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxRetryCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Or for Azure SQL with optimized settings&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHoneyDrunkDataAzureSql&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureSql"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Model conventions automatically apply SQL Server best practices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnModelCreating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelBuilder&lt;/span&gt; &lt;span class="n"&gt;modelBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;modelBuilder&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseDateTime2ForAllDateTimeProperties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// No more datetime truncation&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureDecimalPrecision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// Explicit money precision&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full stack with SQL Server&lt;/span&gt;
dotnet add package HoneyDrunk.Data.SqlServer

&lt;span class="c"&gt;# Or just EF Core (bring your own provider)&lt;/span&gt;
dotnet add package HoneyDrunk.Data.EntityFramework

&lt;span class="c"&gt;# Or abstractions only (for domain libraries)&lt;/span&gt;
dotnet add package HoneyDrunk.Data.Abstractions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Minimal setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Register Kernel (required for context propagation)&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"order-service"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StudioId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"acme-corp"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Register Data layer&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHoneyDrunkDataSqlServer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders/{id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IUnitOfWork&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;unitOfWork&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;unitOfWork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;FindByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;This is v0.1.0—the foundation. On the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL provider&lt;/strong&gt; (&lt;code&gt;HoneyDrunk.Data.PostgreSQL&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read replica support&lt;/strong&gt; for CQRS patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outbox pattern&lt;/strong&gt; integration for reliable messaging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query profiling&lt;/strong&gt; with automatic slow query detection&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/HoneyDrunkStudios/HoneyDrunk.Data" rel="noopener noreferrer"&gt;github.com/HoneyDrunkStudios/HoneyDrunk.Data&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NuGet:&lt;/strong&gt; &lt;code&gt;HoneyDrunk.Data.SqlServer&lt;/code&gt; (and friends)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://github.com/HoneyDrunkStudios/HoneyDrunk.Data/blob/main/HoneyDrunk.Data/docs/FILE_GUIDE.md" rel="noopener noreferrer"&gt;FILE_GUIDE.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built with 🍯 by &lt;a href="https://github.com/HoneyDrunkStudios" rel="noopener noreferrer"&gt;HoneyDrunk Studios&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Got questions? Open an issue or find us on GitHub. PRs welcome—especially if you want to add that PostgreSQL provider.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://tatteddev.com/blog/honeydrunk-data-multi-tenant-persistence-layer/" rel="noopener noreferrer"&gt;https://tatteddev.com/blog/honeydrunk-data-multi-tenant-persistence-layer/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>dotnet</category>
      <category>programming</category>
      <category>data</category>
    </item>
    <item>
      <title>HoneyDrunk.Transport: A Transport Agnostic Messaging Framework for .NET</title>
      <dc:creator>Tatted Dev</dc:creator>
      <pubDate>Mon, 08 Dec 2025 13:56:08 +0000</pubDate>
      <link>https://forem.com/tatted_dev/honeydrunktransport-a-transport-agnostic-messaging-framework-for-net-2lca</link>
      <guid>https://forem.com/tatted_dev/honeydrunktransport-a-transport-agnostic-messaging-framework-for-net-2lca</guid>
      <description>&lt;p&gt;Distributed systems love to trap you in vendor gravity. One transport today becomes a rewrite tomorrow. When I started wiring together services for HoneyDrunk’s distributed grid, the usual messaging libraries all had the same problem: coupling. Everything leaked broker assumptions.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;HoneyDrunk.Transport&lt;/strong&gt; to break that pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Is
&lt;/h2&gt;

&lt;p&gt;HoneyDrunk.Transport is a transport-agnostic messaging abstraction for .NET. Your code looks identical whether you're using Azure Service Bus, Azure Storage Queues, or the InMemory transport.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This code works identically whether you're using&lt;/span&gt;
&lt;span class="c1"&gt;// Azure Service Bus, Storage Queues, or InMemory transport&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMessagePublisher&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IGridContext&lt;/span&gt; &lt;span class="n"&gt;gridContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateOrderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PublishAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"orders.created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;gridContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gridContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your handlers stay the same too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderCreatedHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IMessageHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderCreated&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MessageProcessingResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;OrderCreated&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MessageContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;correlationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GridContext&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;CorrelationId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GridContext&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;TenantId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessOrderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MessageProcessingResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why It Exists
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Swap transports without rewriting code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Development&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkTransportCore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkInMemoryTransport&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Production&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkTransportCore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkServiceBusTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FullyQualifiedNamespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mynamespace.servicebus.windows.net"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"orders-queue"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Distributed context propagation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Node A (Order Service)                    Node B (Payment Service)
┌─────────────────────┐                   ┌─────────────────────┐
│ CorrelationId: abc  │ ──── Queue ────▶  │ CorrelationId: abc  │
│ TenantId: tenant-1  │                   │ TenantId: tenant-1  │
│ ProjectId: proj-1   │                   │ ProjectId: proj-1   │
└─────────────────────┘                   └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Middleware pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkTransportCore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnableTelemetry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// OpenTelemetry spans&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnableLogging&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Structured logging&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnableCorrelation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Grid context propagation&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Add custom middleware&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddMessageMiddleware&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TenantResolutionMiddleware&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Configurable error handling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConfigurableErrorHandlingStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxDeliveryCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetryOn&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetryOn&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeadLetterOn&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ValidationException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeadLetterOn&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IErrorHandlingStrategy&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architecture at a Glance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                     Your Application                            │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐ │
│  │ OrderService    │  │ PaymentHandler  │  │ Custom          │ │
│  │ (IMessagePub)   │  │ (IMessageHandler)│ │ Middleware      │ │
│  └────────┬────────┘  └────────┬────────┘  └────────┬────────┘ │
└───────────┼─────────────────────┼─────────────────────┼─────────┘
            │                     │                     │
┌───────────▼─────────────────────▼─────────────────────▼─────────┐
│                   HoneyDrunk.Transport                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │ Abstractions │  │   Pipeline   │  │ Configuration│          │
│  │ IMessagePub  │  │  Middleware  │  │ ErrorStrategy│          │
│  │ IMessageHdlr │  │  Telemetry   │  │ RetryOptions │          │
│  └──────────────┘  └──────────────┘  └──────────────┘          │
└─────────────────────────────┬───────────────────────────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
┌───────────────┐    ┌───────────────┐    ┌───────────────┐
│ ServiceBus    │    │ StorageQueue  │    │   InMemory    │
│   Transport   │    │   Transport   │    │   Transport   │
└───────────────┘    └───────────────┘    └───────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Transport Adapters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Azure Service Bus
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkServiceBusTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FullyQualifiedNamespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mynamespace.servicebus.windows.net"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EntityType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ServiceBusEntityType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"orders-topic"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubscriptionName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"order-processor"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxConcurrency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Blob fallback for large messages&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BlobFallback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BlobFallback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccountUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://myaccount.blob.core.windows.net"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Azure Storage Queue
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkStorageQueueTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueueName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"notifications"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxConcurrency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BatchProcessingConcurrency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 40 total concurrent&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  InMemory Transport
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkTransportCore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHoneyDrunkInMemoryTransport&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package HoneyDrunk.Transport
dotnet add package HoneyDrunk.Transport.AzureServiceBus
dotnet add package HoneyDrunk.Transport.StorageQueue
dotnet add package HoneyDrunk.Transport.InMemory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;HoneyDrunk.Transport exists because messaging shouldn’t require allegiance to a single broker. If you want clean abstractions, portable handlers, built-in telemetry, and automatic context propagation, it’s worth exploring.&lt;/p&gt;

&lt;p&gt;If you try it, let me know how it fits into your stack.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;strong&gt;&lt;a href="https://tatteddev.com/blog/honeydrunk-transport-messaging-framework/" rel="noopener noreferrer"&gt;TattedDev.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>opensource</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Honeypunk: A Semantic, Palette-Driven Theme Pipeline for Visual Studio 2026</title>
      <dc:creator>Tatted Dev</dc:creator>
      <pubDate>Sun, 16 Nov 2025 20:43:39 +0000</pubDate>
      <link>https://forem.com/tatted_dev/honeypunk-a-semantic-palette-driven-theme-pipeline-for-visual-studio-2026-1a5k</link>
      <guid>https://forem.com/tatted_dev/honeypunk-a-semantic-palette-driven-theme-pipeline-for-visual-studio-2026-1a5k</guid>
      <description>&lt;p&gt;Visual Studio 2026 shipped last week, and like a lot of devs, I installed it immediately. The new UI polish looked great. The editor surfaces felt modern. Then I opened a file and hit something familiar:&lt;/p&gt;

&lt;p&gt;My favorite themes had not updated.&lt;/p&gt;

&lt;p&gt;No previews. No experimental builds. No support for the new classification keys introduced in VS2026.&lt;/p&gt;

&lt;p&gt;Instead of waiting, I built the update myself and, in the process, created a full semantic theme pipeline.&lt;/p&gt;

&lt;p&gt;Honeypunk is a palette-driven, YAML-based system that lets you define color intent once and generate a complete Visual Studio theme automatically. No hex hunting. No drift. No fragile edits.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/tatteddev/Honeypunk" rel="noopener noreferrer"&gt;https://github.com/tatteddev/Honeypunk&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Manual Theme Editing Fails
&lt;/h2&gt;

&lt;p&gt;Visual Studio theming is powerful but tedious. Updating themes manually is slow and error prone, especially when a major version adds new editor surfaces.&lt;/p&gt;

&lt;p&gt;Common issues include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hex values scattered across huge JSON theme files
&lt;/li&gt;
&lt;li&gt;Small tweaks creating near-duplicate colors
&lt;/li&gt;
&lt;li&gt;New VS releases breaking old themes
&lt;/li&gt;
&lt;li&gt;One color change often requiring 20 or more key edits
&lt;/li&gt;
&lt;li&gt;No auditing for unused or duplicate colors
&lt;/li&gt;
&lt;li&gt;Experimentation requiring too much overhead
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honeypunk introduces a semantic, declarative approach that avoids all of this.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pipeline Model
&lt;/h2&gt;

&lt;p&gt;Honeypunk follows a predictable, declarative flow:&lt;/p&gt;

&lt;p&gt;Palette → Roles → Mappings → Build → VSIX&lt;/p&gt;

&lt;h3&gt;
  
  
  Palette
&lt;/h3&gt;

&lt;p&gt;The palette is the root of the system. All colors are centralized into a single file with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human-readable names
&lt;/li&gt;
&lt;li&gt;Contrast rules
&lt;/li&gt;
&lt;li&gt;Usage intent (foreground, accent, diagnostic, background)
&lt;/li&gt;
&lt;li&gt;Structure that allows global shifts with a single edit
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Roles
&lt;/h3&gt;

&lt;p&gt;Roles describe what a token represents, not what color it uses. Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;function
&lt;/li&gt;
&lt;li&gt;variable
&lt;/li&gt;
&lt;li&gt;type
&lt;/li&gt;
&lt;li&gt;interface
&lt;/li&gt;
&lt;li&gt;keyword
&lt;/li&gt;
&lt;li&gt;comment
&lt;/li&gt;
&lt;li&gt;string
&lt;/li&gt;
&lt;li&gt;operator
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Roles point to palette entries, never hex values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mappings
&lt;/h3&gt;

&lt;p&gt;Mappings connect semantic roles to actual Visual Studio classification keys. This file handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VS2022 through VS2026 classifications
&lt;/li&gt;
&lt;li&gt;ReSharper keys
&lt;/li&gt;
&lt;li&gt;Background and foreground tokens
&lt;/li&gt;
&lt;li&gt;New surfaces introduced by 2026
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build Scripts
&lt;/h3&gt;

&lt;p&gt;The Python tooling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loads palette, roles, mappings
&lt;/li&gt;
&lt;li&gt;Resolves semantic intent to actual colors
&lt;/li&gt;
&lt;li&gt;Validates for errors, typos, unmapped items
&lt;/li&gt;
&lt;li&gt;Preserves flags like transparency
&lt;/li&gt;
&lt;li&gt;Generates a deterministic theme file
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  VSIX
&lt;/h3&gt;

&lt;p&gt;The final packaging step creates a distributable Visual Studio extension supporting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VS2022
&lt;/li&gt;
&lt;li&gt;VS2025
&lt;/li&gt;
&lt;li&gt;VS2026
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire pipeline allows rebuilds in seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Files
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Honeypunk.yaml
&lt;/h3&gt;

&lt;p&gt;Defines high-level theme structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Editor
&lt;/li&gt;
&lt;li&gt;Chrome and toolbars
&lt;/li&gt;
&lt;li&gt;Tool windows
&lt;/li&gt;
&lt;li&gt;Accent states
&lt;/li&gt;
&lt;li&gt;Debugging and caret surfaces
&lt;/li&gt;
&lt;li&gt;Selection and inlay hints
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  palette.md
&lt;/h3&gt;

&lt;p&gt;A complete list of named colors with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hex values
&lt;/li&gt;
&lt;li&gt;Usage recommendations
&lt;/li&gt;
&lt;li&gt;Contrast notes
&lt;/li&gt;
&lt;li&gt;Accessibility considerations
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  roles.yaml
&lt;/h3&gt;

&lt;p&gt;Maps semantic intent to palette names, for example:&lt;/p&gt;

&lt;p&gt;functions: NeonYellow&lt;br&gt;&lt;br&gt;
variables: ChromeTeal&lt;br&gt;&lt;br&gt;
comments: SlateGray  &lt;/p&gt;

&lt;h3&gt;
  
  
  mappings.yaml
&lt;/h3&gt;

&lt;p&gt;Links roles to actual Visual Studio classification keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  apply_palette.py
&lt;/h3&gt;

&lt;p&gt;Reads all YAML files, applies semantic roles, enforces safety checks, and produces the resolved theme.&lt;/p&gt;

&lt;h3&gt;
  
  
  build_vsix.py
&lt;/h3&gt;

&lt;p&gt;Packages the final theme into a VSIX with the necessary manifest metadata.&lt;/p&gt;




&lt;h2&gt;
  
  
  Palette Philosophy
&lt;/h2&gt;

&lt;p&gt;The Honeypunk palette is intentionally structured for clarity and reduced fatigue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background tier
&lt;/h3&gt;

&lt;p&gt;Deep gunmetal and charcoal tones that minimize glare.&lt;/p&gt;

&lt;h3&gt;
  
  
  Text tier
&lt;/h3&gt;

&lt;p&gt;Neutral off-white and slate values optimized for long reading sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accent tier
&lt;/h3&gt;

&lt;p&gt;Electric blues, neon yellows, and magentas used sparingly and intentionally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnostic triad
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Orange for warnings
&lt;/li&gt;
&lt;li&gt;Magenta for attention
&lt;/li&gt;
&lt;li&gt;Green for success
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The palette supports fast code scanning and visual consistency across large files.&lt;/p&gt;




&lt;h2&gt;
  
  
  Updating for Visual Studio 2026
&lt;/h2&gt;

&lt;p&gt;VS2026 introduced new:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Classification keys
&lt;/li&gt;
&lt;li&gt;Chrome surfaces
&lt;/li&gt;
&lt;li&gt;Editor states
&lt;/li&gt;
&lt;li&gt;Accent highlights
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A manual update would require reviewing every affected surface.&lt;br&gt;&lt;br&gt;
Honeypunk reduced this to a few mapping adjustments and a single rebuild.&lt;/p&gt;




&lt;h2&gt;
  
  
  Extending the System
&lt;/h2&gt;

&lt;p&gt;Honeypunk was built for growth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a new role
&lt;/h3&gt;

&lt;p&gt;Add to roles.yaml and map in mappings.yaml.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a new color
&lt;/h3&gt;

&lt;p&gt;Add to palette.md and reference it in roles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple theme variants
&lt;/h3&gt;

&lt;p&gt;Create separate YAML build configurations sharing the same palette.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coming soon
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Accessibility variants
&lt;/li&gt;
&lt;li&gt;JSON diff reports
&lt;/li&gt;
&lt;li&gt;Live preview experiments
&lt;/li&gt;
&lt;li&gt;CI checks for color drift
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Common fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Theme not showing: verify VSIX manifest GUID.
&lt;/li&gt;
&lt;li&gt;Odd colors: check palette name for typos.
&lt;/li&gt;
&lt;li&gt;Build failure: YAML indentation error.
&lt;/li&gt;
&lt;li&gt;Missing token updates: mapping entry missing.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Clone the repo and modify a single role.&lt;br&gt;&lt;br&gt;
Rebuild the VSIX.&lt;br&gt;&lt;br&gt;
Install and restart Visual Studio.  &lt;/p&gt;

&lt;p&gt;You will immediately see how semantic theming makes iteration effortless.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/tatteddev/Honeypunk" rel="noopener noreferrer"&gt;https://github.com/tatteddev/Honeypunk&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Originally published at:&lt;br&gt;&lt;br&gt;
&lt;a href="https://tatteddev.com/blog/honeypunk-semantic-theme-pipeline-vs2026/" rel="noopener noreferrer"&gt;https://tatteddev.com/blog/honeypunk-semantic-theme-pipeline-vs2026/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>themes</category>
      <category>programming</category>
      <category>automation</category>
    </item>
    <item>
      <title>Building a Zero-Configuration .NET Standards Package</title>
      <dc:creator>Tatted Dev</dc:creator>
      <pubDate>Mon, 03 Nov 2025 21:58:37 +0000</pubDate>
      <link>https://forem.com/tatted_dev/building-a-zero-configuration-net-standards-package-2fgl</link>
      <guid>https://forem.com/tatted_dev/building-a-zero-configuration-net-standards-package-2fgl</guid>
      <description>&lt;p&gt;&lt;strong&gt;Posted by &lt;a href="https://tatteddev.com" rel="noopener noreferrer"&gt;TattedDev&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Code Standards
&lt;/h2&gt;

&lt;p&gt;Every .NET organization eventually fights the same battle: keeping code quality consistent across dozens of solutions. You copy &lt;code&gt;.editorconfig&lt;/code&gt; files, manually add analyzers, and hope developers remember to fix warnings. Over time, configurations drift, rules diverge, and build quality becomes uneven.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/HoneyDrunkStudios/HoneyDrunk.Standards" rel="noopener noreferrer"&gt;HoneyDrunk.Standards&lt;/a&gt;&lt;/strong&gt; solves this by turning code quality into infrastructure. It’s a single NuGet package that enforces conventions, analyzers, and build configurations automatically across every project in your ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Build-Transitive Package Design
&lt;/h3&gt;

&lt;p&gt;The package uses &lt;strong&gt;MSBuild’s &lt;code&gt;buildTransitive&lt;/code&gt; feature&lt;/strong&gt;, which allows its build logic to propagate to all downstream projects that reference it.&lt;br&gt;&lt;br&gt;
Unlike normal runtime packages, this one operates entirely at build time.&lt;/p&gt;

&lt;p&gt;When referenced, it automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enables &lt;strong&gt;StyleCop.Analyzers&lt;/strong&gt; and &lt;strong&gt;Microsoft.CodeAnalysis.NetAnalyzers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enforces &lt;strong&gt;nullable reference types&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Treats &lt;strong&gt;warnings as errors&lt;/strong&gt; in CI environments&lt;/li&gt;
&lt;li&gt;Enables &lt;strong&gt;deterministic builds&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Locks &lt;strong&gt;C# language version&lt;/strong&gt; to &lt;code&gt;latest&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t modify your &lt;code&gt;.csproj&lt;/code&gt;. You don’t merge &lt;code&gt;.editorconfig&lt;/code&gt;. It’s zero-configuration by design.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. Continuous Integration Awareness
&lt;/h3&gt;

&lt;p&gt;The package detects CI systems (like GitHub Actions or Azure Pipelines) through environment variables and automatically enables &lt;code&gt;ContinuousIntegrationBuild=true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This provides two distinct build profiles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local development:&lt;/strong&gt; fast incremental builds with absolute source paths for debugging
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI builds:&lt;/strong&gt; deterministic, reproducible outputs with embedded source paths for traceability
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is parity between developer machines and build servers without requiring conditional logic in your projects.&lt;/p&gt;


&lt;h3&gt;
  
  
  3. Opt-Out Instead of Opt-In
&lt;/h3&gt;

&lt;p&gt;Every enforcement feature is enabled by default.&lt;br&gt;&lt;br&gt;
Developers can selectively disable rules via MSBuild properties, but the baseline assumes quality enforcement. This approach prevents “silent drift” where one repository quietly stops following standards.&lt;/p&gt;

&lt;p&gt;Opt-out defaults ensure consistency across all solutions while maintaining flexibility for legitimate edge cases.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why This Approach Works
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Consistent Code Quality
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;StyleCop.Analyzers enforces over 100 naming, spacing, and ordering rules
&lt;/li&gt;
&lt;li&gt;Microsoft.CodeAnalysis.NetAnalyzers enforces performance, security, and reliability best practices
&lt;/li&gt;
&lt;li&gt;Violations fail CI builds automatically, maintaining organizational quality thresholds
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Configuration Centralization
&lt;/h3&gt;

&lt;p&gt;One version bump updates analyzers, language rules, and conventions across every project.&lt;br&gt;&lt;br&gt;
No more synchronizing &lt;code&gt;.editorconfig&lt;/code&gt; files or manually aligning rule sets across repositories.&lt;/p&gt;
&lt;h3&gt;
  
  
  Faster Onboarding
&lt;/h3&gt;

&lt;p&gt;New developers get immediate feedback inside Visual Studio or Rider.&lt;br&gt;&lt;br&gt;
Standards become visible and actionable in the IDE instead of hidden in documentation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Deterministic and Auditable Builds
&lt;/h3&gt;

&lt;p&gt;Every project builds reproducibly, producing identical binaries for the same source inputs.&lt;br&gt;&lt;br&gt;
That’s essential for compliance, debugging, and verifying production deployments.&lt;/p&gt;


&lt;h2&gt;
  
  
  Common Gotchas and Solutions
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Large Warning Backlogs
&lt;/h3&gt;

&lt;p&gt;Adding the package to a legacy project often triggers hundreds of analyzer warnings.&lt;br&gt;&lt;br&gt;
Use incremental adoption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;NoWarn&amp;gt;&lt;/span&gt;$(NoWarn);SA*&lt;span class="nt"&gt;&amp;lt;/NoWarn&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then remove suppression rules as you clean up the codebase.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Missing &lt;code&gt;PrivateAssets="all"&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Omitting this property allows analyzers to flow transitively into your consumers, which is rarely desirable.&lt;/p&gt;

&lt;p&gt;Always declare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"HoneyDrunk.Standards"&lt;/span&gt; &lt;span class="na"&gt;PrivateAssets=&lt;/span&gt;&lt;span class="s"&gt;"all"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents analyzer propagation and keeps the standards internal to your organization.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Test Method Naming (CA1707)
&lt;/h3&gt;

&lt;p&gt;Unit test naming conventions like &lt;code&gt;MethodName_Condition_ExpectedResult&lt;/code&gt; conflict with CA1707.&lt;br&gt;&lt;br&gt;
The package pre-suppresses this rule, prioritizing readability and established testing patterns.&lt;/p&gt;


&lt;h3&gt;
  
  
  4. Overuse of the Null-Forgiving Operator (&lt;code&gt;!&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The null-forgiving operator should be reserved for proven non-null scenarios.&lt;br&gt;&lt;br&gt;
Document any usage with an inline comment explaining why null cannot occur.&lt;/p&gt;


&lt;h3&gt;
  
  
  5. Local vs CI Output Differences
&lt;/h3&gt;

&lt;p&gt;When running locally, builds embed absolute file paths.&lt;br&gt;&lt;br&gt;
In CI mode, paths are normalized for reproducibility.&lt;/p&gt;

&lt;p&gt;To simulate CI locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet build /p:ContinuousIntegrationBuild&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  6. Team Preference Conflicts
&lt;/h3&gt;

&lt;p&gt;Different teams sometimes prefer conflicting style rules (for example, requiring or omitting &lt;code&gt;this.&lt;/code&gt; prefixes).&lt;br&gt;&lt;br&gt;
The package allows per-project overrides in &lt;code&gt;.editorconfig&lt;/code&gt;, and StyleCop can be disabled completely if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;EnableStyleCopAnalyzers&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/EnableStyleCopAnalyzers&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Implementation Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with a sample project.&lt;/strong&gt; The included &lt;code&gt;Consumer.Sample&lt;/code&gt; demonstrates compliant patterns and deliberate violations.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document your rationale.&lt;/strong&gt; The &lt;code&gt;CONVENTIONS.md&lt;/code&gt; file explains why specific rules exist.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use versioning semantics.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Minor version → new rules or non-breaking changes
&lt;/li&gt;
&lt;li&gt;Major version → breaking rule severity or enforcement changes
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Treat standards packages as &lt;strong&gt;infrastructure&lt;/strong&gt;, not libraries. They’re a versioned part of your build system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Build-transitive packages are the cleanest way to enforce organization-wide build and analyzer standards.
&lt;/li&gt;
&lt;li&gt;Default-on enforcement guarantees consistency.
&lt;/li&gt;
&lt;li&gt;Gradual adoption keeps legacy code manageable.
&lt;/li&gt;
&lt;li&gt;Documentation matters as much as enforcement.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PrivateAssets="all"&lt;/code&gt; prevents downstream dependency pollution.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"HoneyDrunk.Standards"&lt;/span&gt; &lt;span class="na"&gt;PrivateAssets=&lt;/span&gt;&lt;span class="s"&gt;"all"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single line brings your organization in line with modern, deterministic .NET build standards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/HoneyDrunkStudios/HoneyDrunk.Standards" rel="noopener noreferrer"&gt;github.com/HoneyDrunkStudios/HoneyDrunk.Standards&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; Conventions guide, naming policies, nullable handling, and migration strategies.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you implemented your own internal standards package? What challenges did you hit during rollout? Share them below — the goal is a future where “code style review” isn’t a recurring meeting topic.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;🟡 &lt;em&gt;Written by &lt;a href="https://tatteddev.com" rel="noopener noreferrer"&gt;TattedDev&lt;/a&gt; — building the HoneyDrunk ecosystem, one node at a time.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>devops</category>
      <category>codequality</category>
    </item>
    <item>
      <title>HoneyDrunk.Pipelines — Treat Your CI/CD as Infrastructure</title>
      <dc:creator>Tatted Dev</dc:creator>
      <pubDate>Sat, 11 Oct 2025 21:02:40 +0000</pubDate>
      <link>https://forem.com/tatted_dev/honeydrunkpipelines-treat-your-cicd-as-infrastructure-38p2</link>
      <guid>https://forem.com/tatted_dev/honeydrunkpipelines-treat-your-cicd-as-infrastructure-38p2</guid>
      <description>&lt;p&gt;When you’re a small team—or even a solo dev—the friction of managing CI/CD pipelines across multiple repos adds up fast.&lt;br&gt;&lt;br&gt;
A missing permission, a stale PAT, or a malformed GUID can break your release.  &lt;/p&gt;

&lt;p&gt;That’s what pushed me to think differently: &lt;strong&gt;treat pipeline configuration like infrastructure you version, maintain, and compose&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;In this post, I’ll walk through how I built &lt;strong&gt;HoneyDrunk.Pipelines&lt;/strong&gt;, a centralized, reusable template system for Azure DevOps, and the lessons that came out of it.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧠 The Problem: Pipeline Sprawl &amp;amp; Runtime Breakage
&lt;/h2&gt;

&lt;p&gt;Before building a unified system, here’s what I was dealing with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every repo had its own &lt;code&gt;azure-pipelines.yml&lt;/code&gt;, each slightly different
&lt;/li&gt;
&lt;li&gt;Copy-pasted feed GUIDs, mismatched naming, and inconsistent versioning
&lt;/li&gt;
&lt;li&gt;Permission and service connection issues that only surfaced at runtime
&lt;/li&gt;
&lt;li&gt;Changing SDK versions or build logic meant updating several repos manually
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single pipeline failure captured the pain perfectly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The build service identity didn’t have &lt;strong&gt;Contributor&lt;/strong&gt; on the feed
&lt;/li&gt;
&lt;li&gt;The service connection used a stale PAT
&lt;/li&gt;
&lt;li&gt;The feed URL (with GUID) was mis-typed
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these were independent problems that only appeared &lt;strong&gt;after&lt;/strong&gt; I pushed.&lt;br&gt;&lt;br&gt;
That’s when I decided: pipelines can’t be snowflakes anymore.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚙️ The Vision: Pipelines as Reusable Infrastructure
&lt;/h2&gt;

&lt;p&gt;I wrote down a few core principles before rebuilding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single source of truth.&lt;/strong&gt; One repo for templates, many consumers
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioned logic.&lt;/strong&gt; Pipeline changes are reviewed and auditable
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composable building blocks.&lt;/strong&gt; Stages, jobs, and steps can be mixed and matched
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns.&lt;/strong&gt; Projects define &lt;em&gt;what&lt;/em&gt; to build; templates define &lt;em&gt;how&lt;/em&gt; to build
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  🗂️ Repository Layout (Simplified)
&lt;/h3&gt;

&lt;p&gt;HoneyDrunk.Pipelines/&lt;br&gt;
├── stages/&lt;br&gt;
├── jobs/&lt;br&gt;
├── steps/&lt;br&gt;
└── README.md&lt;/p&gt;

&lt;p&gt;ConsumerRepo/&lt;br&gt;
└── azure-pipelines.yml # references templates from HoneyDrunk.Pipelines&lt;/p&gt;

&lt;p&gt;Consumer repos reference the template repo via Azure DevOps resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pipelines&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HoneyDrunk/HoneyDrunk.Pipelines&lt;/span&gt;
      &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;refs/heads/main&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stages/pr-validation.stage.yaml@pipelines&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;projectPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;src/MyLib/MyLib.csproj'&lt;/span&gt;
      &lt;span class="na"&gt;runTests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stages/dotnet-publish.stage.yaml@pipelines&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PR_Validation&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))&lt;/span&gt;
      &lt;span class="na"&gt;packagePath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.ArtifactStagingDirectory)/**/*.nupkg'&lt;/span&gt;
      &lt;span class="na"&gt;feedName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HoneyDrunk-Internal'&lt;/span&gt;


&lt;span class="s"&gt;That’s about 10 lines of YAML per project — the rest lives in the infrastructure layer.&lt;/span&gt;

&lt;span class="s"&gt;🧩 Core Template Mechanics&lt;/span&gt;
&lt;span class="s"&gt;PR Validation Stage&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;projectPath&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;configuration&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Release'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;runTests&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnetVersion&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9.x'&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PR_Validation&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Build,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Test,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Validate'&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt;
        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../steps/install-dotnet-sdk.step.yaml&lt;/span&gt;
            &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;dotnetVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.dotnetVersion }}&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../steps/dotnet-restore-build.step.yaml&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../steps/dotnet-build.step.yaml&lt;/span&gt;
            &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;projectPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.projectPath }}&lt;/span&gt;
              &lt;span class="na"&gt;configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.configuration }}&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${{ if eq(parameters.runTests, &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&gt;) }}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../steps/dotnet-test.step.yaml&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../steps/dotnet-pack.step.yaml&lt;/span&gt;
            &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;projectPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.projectPath }}&lt;/span&gt;
              &lt;span class="na"&gt;configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.configuration }}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step is modular, reusable, and easy to evolve.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;Package Publish Step&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;packagePath&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;feedName&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serviceConnection&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HoneyDrunk-AzureDevOps'&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NuGetAuthenticate@1&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authenticate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Azure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Artifacts'&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;nuGetServiceConnections&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.serviceConnection }}&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DotNetCoreCLI@2&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;parameters.feedName&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;push'&lt;/span&gt;
      &lt;span class="na"&gt;packagesToPush&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.packagePath }}&lt;/span&gt;
      &lt;span class="na"&gt;nuGetFeedType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;internal'&lt;/span&gt;
      &lt;span class="na"&gt;publishVstsFeed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HoneyDrunk/${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;parameters.feedName&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
      &lt;span class="na"&gt;allowPackageConflicts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to avoid brittle GUIDs, this script dynamically builds the feed URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;FEED_URL="https://pkgs.dev.azure.com/$(System.TeamFoundationCollectionUri | sed 's|https://dev.azure.com/||')/_packaging/${{ parameters.feedName }}/nuget/v3/index.json"&lt;/span&gt;
    &lt;span class="s"&gt;echo "##vso[task.setvariable variable=FeedUrl]$FEED_URL"&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Compute&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Feed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;URL'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more copy-paste errors, no more mismatched feed identifiers.&lt;/p&gt;

&lt;p&gt;🧰 Troubleshooting &amp;amp; Lessons Learned&lt;br&gt;
Permission Setup&lt;/p&gt;

&lt;p&gt;Ensure your Azure Pipelines build identity (Project Build Service) has Contributor access to the feed.&lt;br&gt;
Without it, pushes will silently fail.&lt;/p&gt;

&lt;p&gt;Service Connections&lt;/p&gt;

&lt;p&gt;Use NuGetAuthenticate@1 instead of personal PATs wherever possible — it leverages the build service identity.&lt;/p&gt;

&lt;p&gt;Parameterization&lt;/p&gt;

&lt;p&gt;Anything that can change — paths, feed names, versions — should be a parameter.&lt;br&gt;
Hardcoded values are time bombs.&lt;/p&gt;

&lt;p&gt;🚀 The Impact&lt;/p&gt;

&lt;p&gt;✅ Shared templates mean all projects follow the same structure&lt;br&gt;
✅ Versioning and naming stay consistent&lt;br&gt;
✅ Permission errors are practically gone&lt;br&gt;
✅ Onboarding a new project takes minutes&lt;/p&gt;

&lt;p&gt;⚖️ Before vs After&lt;/p&gt;

&lt;p&gt;Before&lt;/p&gt;

&lt;p&gt;Copy-paste massive YAML files&lt;/p&gt;

&lt;p&gt;Search &amp;amp; replace project names&lt;/p&gt;

&lt;p&gt;Debug GUIDs and PATs by trial and error&lt;/p&gt;

&lt;p&gt;After&lt;/p&gt;

&lt;p&gt;Reference templates&lt;/p&gt;

&lt;p&gt;Set parameters&lt;/p&gt;

&lt;p&gt;Push → it just works&lt;/p&gt;

&lt;p&gt;🐝 Why It Matters for Small Teams&lt;/p&gt;

&lt;p&gt;For indie devs or small studios like mine:&lt;/p&gt;

&lt;p&gt;Time is precious. Every failed build costs momentum&lt;/p&gt;

&lt;p&gt;Context switching is brutal. You shouldn’t be debugging YAML daily&lt;/p&gt;

&lt;p&gt;Future-you will forget. Document it once, reuse forever&lt;/p&gt;

&lt;p&gt;Treating pipelines as infrastructure is how you scale solo work.&lt;/p&gt;

&lt;p&gt;🔮 Next Steps&lt;/p&gt;

&lt;p&gt;Auto-generate release notes from commits&lt;/p&gt;

&lt;p&gt;Add semantic versioning via conventional commits&lt;/p&gt;

&lt;p&gt;Extend templates to handle multiple languages and deployment targets&lt;/p&gt;

&lt;p&gt;Build once. Reuse forever.&lt;br&gt;
That’s how HoneyDrunk Studios automates the grid.&lt;/p&gt;

&lt;p&gt;🧱 Originally published on &lt;a href="https://www.tatteddev.com/blog/honeypipelines-automating-the-grid/" rel="noopener noreferrer"&gt;TattedDev.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ci</category>
      <category>devops</category>
      <category>azure</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Avoiding the 'Common' Trap: How I Structure Shared Packages at HoneyDrunk</title>
      <dc:creator>Tatted Dev</dc:creator>
      <pubDate>Wed, 01 Oct 2025 20:48:59 +0000</pubDate>
      <link>https://forem.com/tatted_dev/avoiding-the-common-trap-how-i-structure-shared-packages-at-honeydrunk-4agi</link>
      <guid>https://forem.com/tatted_dev/avoiding-the-common-trap-how-i-structure-shared-packages-at-honeydrunk-4agi</guid>
      <description>&lt;p&gt;When you’re building something big, it’s easy to fall into the &lt;strong&gt;“just toss it into Common”&lt;/strong&gt; trap.&lt;br&gt;&lt;br&gt;
You know the one — a giant “Shared” library where every random helper, DTO, and extension method ends up living rent-free.  &lt;/p&gt;

&lt;p&gt;That’s fine until you wake up one day and realize you’ve created a god-class library that nobody understands and everybody hates.  &lt;/p&gt;

&lt;p&gt;I didn’t want that. HoneyDrunk (my indie dev playground) is meant to scale cleanly, so I sat down and mapped out what the shared package ecosystem should actually look like.&lt;br&gt;&lt;br&gt;
This post is me brain-dumping how I’m structuring it, so future-me doesn’t scream at past-me later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core First, Junk Drawer Never
&lt;/h2&gt;

&lt;p&gt;The heart of the platform starts with fundamentals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Core&lt;/code&gt; → base entities, domain events, common exceptions
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Validation&lt;/code&gt; → validators and rule sets
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Security&lt;/code&gt; → Argon2 hashing, JWT utilities, auth helpers
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Caching&lt;/code&gt; → Redis + memory abstractions
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Configuration&lt;/code&gt; → Key Vault + strongly typed configs
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.FeatureFlags&lt;/code&gt; → clean feature toggles
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;primitives, not business logic&lt;/strong&gt;. They’re the Lego bricks everything else is built on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Data + Networking
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Data&lt;/code&gt; → EF Core patterns, DbContext helpers, migrations
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Storage&lt;/code&gt; → blob/S3/local filesystem abstraction
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Http&lt;/code&gt; → resilient HttpClient factory (retry, jitter, circuit breaker)
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.RestService&lt;/code&gt; → less boilerplate over &lt;code&gt;Http&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Grpc&lt;/code&gt; → shared contracts + helpers
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.SignalR&lt;/code&gt; → hub contracts + client utilities
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Messaging vs Notifications
&lt;/h2&gt;

&lt;p&gt;Here’s where people mess it up. &lt;strong&gt;Messaging ≠ Notifications.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Messaging&lt;/code&gt; → system-to-system (pub/sub, service bus commands)
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Notifications.Email&lt;/code&gt; → user-facing email
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Notifications.Sms&lt;/code&gt; → text messages
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Notifications.Push&lt;/code&gt; → mobile + web push
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 One is about services talking to each other. The other is about talking to humans. Don’t cross the streams.&lt;/p&gt;




&lt;h2&gt;
  
  
  Observability + Quality
&lt;/h2&gt;

&lt;p&gt;If you’re not measuring, you’re guessing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Observability&lt;/code&gt; → Serilog, OpenTelemetry, metrics, tracing
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Testing&lt;/code&gt; → Moq setups, Cypress helpers, fixtures, test utilities
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Commerce (Not Just Payments)
&lt;/h2&gt;

&lt;p&gt;I almost made a &lt;code&gt;HoneyDrunk.Payments&lt;/code&gt; package. Glad I stopped. Payments are just &lt;strong&gt;one part of a bigger story&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Commerce.Orders&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Commerce.Payments&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Commerce.Billing&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That way I can handle orders, invoices, and subscriptions without bending a “Payments” package into something it was never meant to be. &lt;strong&gt;Abstraction matters.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Integrations
&lt;/h2&gt;

&lt;p&gt;These are where the fun APIs live:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Integrations.Streaming&lt;/code&gt; → Twitch, YouTube, Kick
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Integrations.Media&lt;/code&gt; → AniList, TMDB
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Integrations.GamingPlatforms&lt;/code&gt; → Steam, Epic, Xbox APIs
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Integrations.Social&lt;/code&gt; → Discord, Twitter/X, Reddit
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tooling + SDKs
&lt;/h2&gt;

&lt;p&gt;Ops and client devs need love too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Tooling.DevOps&lt;/code&gt; → DACPAC utilities, pipeline helpers, infra scripts
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Sdk.DotNet&lt;/code&gt; → SDK for .NET consumers
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HoneyDrunk.Sdk.JavaScript&lt;/code&gt; (future) → Next.js + Expo SDKs
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Satellite Sites (The Consumers)
&lt;/h2&gt;

&lt;p&gt;All these packages? They’re not the product. They’re the &lt;strong&gt;engine room&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;The actual user-facing pieces are the satellites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt; → public front door
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo mobile app&lt;/strong&gt; → push notifications + community nudges
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blazor admin&lt;/strong&gt; → manage users, payments, bugs
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Astro marketing site&lt;/strong&gt; → blog, docs, branding hub
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚫 Rule #1: satellites don’t talk to databases. They hit APIs/SDKs only.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping It Up
&lt;/h2&gt;

&lt;p&gt;Every package name is a bet on the future.&lt;br&gt;&lt;br&gt;
Go too narrow and you box yourself in. Go too vague and you build a junk drawer.  &lt;/p&gt;

&lt;p&gt;HoneyDrunk is my shot at doing it the right way: &lt;strong&gt;explicit names, clear boundaries, and room to grow without rewriting half the platform.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Abstractions aren’t academic — they’re &lt;strong&gt;strategy&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
And if I screw it up, future-me will definitely roast past-me for it.&lt;/p&gt;




&lt;p&gt;Originally published at: &lt;a href="https://www.tatteddev.com/blog/abstractions-packages-and-satellite-sites/" rel="noopener noreferrer"&gt;https://www.tatteddev.com/blog/abstractions-packages-and-satellite-sites/&lt;/a&gt;  &lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>architecture</category>
      <category>devops</category>
      <category>softwaredesign</category>
    </item>
  </channel>
</rss>
