<?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: James Hickey</title>
    <description>The latest articles on Forem by James Hickey (@jamesmh).</description>
    <link>https://forem.com/jamesmh</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%2F93505%2Fc8881a55-01b1-4504-a36c-24606b1bb3c6.png</url>
      <title>Forem: James Hickey</title>
      <link>https://forem.com/jamesmh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jamesmh"/>
    <language>en</language>
    <item>
      <title>Databases For Web Developers: Indexing</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Fri, 04 Oct 2024 19:37:13 +0000</pubDate>
      <link>https://forem.com/jamesmh/databases-for-web-developers-indexing-305d</link>
      <guid>https://forem.com/jamesmh/databases-for-web-developers-indexing-305d</guid>
      <description>&lt;p&gt;You're a web developer helping to build a growing SaaS application. You've got your back-end programming language and framework under control. You know about building models, controllers, and views.&lt;/p&gt;

&lt;p&gt;Your web framework of choice handles all the database stuff behind the scenes. You're most likely using a relational database like &lt;a href="https://azure.microsoft.com/en-us/products/postgresql?wt.mc_id=DT-MVP-5003843" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt;. It's all good.&lt;/p&gt;

&lt;p&gt;However, your user base has grown. Your product has landed larger customers with more users.&lt;/p&gt;

&lt;p&gt;Now you're facing some new issues with your application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Certain pages on your site take forever to load&lt;/li&gt;
&lt;li&gt;That global search feature in your app is totally useless because it takes too long to load&lt;/li&gt;
&lt;li&gt;Those gnarly multi-filter reports run fine for simple combinations, but add a few more filters and your report crawls to a halt&lt;/li&gt;
&lt;li&gt;Customer-facing analytics/reports for your biggest customers is near impossible to query against without taking minutes to run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;These are all usually symptoms of database issues.&lt;/strong&gt; Some common causes include: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Poorly written queries&lt;/li&gt;
&lt;li&gt;Lack of or misplaced indexes&lt;/li&gt;
&lt;li&gt;Data structures that don't lend themselves to decent performance&lt;/li&gt;
&lt;li&gt;Using the wrong database tool for the job&lt;/li&gt;
&lt;li&gt;Approaches that were fine for expected smaller loads but have outlived their usefulness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want to take you through some of the database techniques, tricks and solutions I wish I knew years ago as a developer helping build multiple SaaS applications. I'll focus on using PostgreSQL as my database of choice - but many of these ideas are transferable.&lt;/p&gt;

&lt;p&gt;We'll start this series with &lt;strong&gt;indexing.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Indexing
&lt;/h2&gt;

&lt;p&gt;I remember some of my earlier jobs trying to get certain queries to perform well.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Maybe if I just change how I'm joining tables it'll go faster?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Maybe if I don't select as much data it'll be faster?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It was like black magic where you had to use trial-and-error to figure out if a query could make things faster.&lt;/p&gt;

&lt;p&gt;I remember specific analytical reports that I couldn't get to work very efficiently. Now that I understand how to use indexes well I know what I should/could have done 🥲.&lt;/p&gt;

&lt;p&gt;One of the learnings that propelled my ability to create efficient SQL queries and build pages/reports that load quickly is learning why and when to apply indexes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Indexes Work
&lt;/h2&gt;

&lt;p&gt;I don't think I need to dig into this too much: just like an index at the back of a book -  database indexes are a structure that allow searching through many records really fast.&lt;/p&gt;

&lt;p&gt;If I want to find which pages in a book talk about "gravity", then instead of searching through &lt;em&gt;every single page&lt;/em&gt;, I can look at the index to see if this topic is listed and which pages I should turn to!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k2byvs928qehtqkv539.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k2byvs928qehtqkv539.png" alt="book index"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Databases have many types of indexes: but understanding the basic index types like &lt;a href="https://en.wikipedia.org/wiki/B-tree" rel="noopener noreferrer"&gt;b-tree&lt;/a&gt; indexes can get you a long way. These are called "b-tree" in &lt;a href="https://www.postgresql.org/docs/current/indexes-types.html#INDEXES-TYPES-BTREE" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt;. In &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/sql-server-index-design-guide?view=sql-server-ver16#nonclustered-index-architecture?wt.mc_id=DT-MVP-5003843" rel="noopener noreferrer"&gt;SQL Server&lt;/a&gt; most indexes are b-tree structures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Index Basics
&lt;/h2&gt;

&lt;p&gt;In an index for a book, you have generally two different pieces to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The book to index&lt;/li&gt;
&lt;li&gt;The term to index&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pages that you need to turn to are the "values" of the index that tell you where to look.&lt;/p&gt;

&lt;p&gt;In a database, the parts of an index that you need to know are similar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The table to index&lt;/li&gt;
&lt;li&gt;The column to index&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "pages" that the query engine needs to look up are found using the values of those indexes. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Yes, in some databases "pages" are ironically the same term for where our rows/records are stored.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmiu96b4g3ntvkofql8o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmiu96b4g3ntvkofql8o.png" alt="index structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In reality, the index doesn't look like the above. It might look like a b-tree. Or, it might be stored some other way as there are many different &lt;em&gt;types&lt;/em&gt; of indexes.&lt;/p&gt;

&lt;p&gt;For example, hash indexes work well for looking up a record by a unique &lt;code&gt;string&lt;/code&gt; (like an email address):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5f68kvbgsuhp5iwpkbud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5f68kvbgsuhp5iwpkbud.png" alt="hash index"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;value&lt;/code&gt; stored is a pointer to where on the database's file storage the row/tuple/record with that specific &lt;code&gt;username&lt;/code&gt; is. The database engine can "look-up" that disk location super quick.&lt;/p&gt;

&lt;p&gt;Otherwise, the database engine would have to &lt;em&gt;loop through all the records in the table&lt;/em&gt; to figure out which one has that specific username.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvs3loguo9k12w257cmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvs3loguo9k12w257cmk.png" alt="hash index"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Column Indexes
&lt;/h2&gt;

&lt;p&gt;Usually, in a book's index, each index is for one term. But imagine (and it probably exists) a book where there are multiple terms that are related to each other quite often: like "space" and "time".&lt;/p&gt;

&lt;p&gt;There might be a "multi-term index" where each index helps you to find sentences where both terms are used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekbl4ebe69x2nj4sm7df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekbl4ebe69x2nj4sm7df.png" alt="multi-term book index"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, you can index multiple database columns in the same index.&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="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;my_index&lt;/span&gt; &lt;span class="k"&gt;on&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;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This type of index can help to support more complex querying scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  How And When To Use An Index
&lt;/h2&gt;

&lt;p&gt;Here are the common cases when it probably makes sense to add an index to a column on a relational table with some tips sprinkled throughout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking up specific records by id
&lt;/h3&gt;

&lt;p&gt;Consider this query:&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="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;id&lt;/code&gt; is a primary key, in all relational databases an index on the &lt;code&gt;id&lt;/code&gt; column is already created for you. This one's easy - you're already covered! &lt;/p&gt;

&lt;p&gt;If an index didn't exist on the &lt;code&gt;id&lt;/code&gt; column, then this query would have to &lt;em&gt;loop through every single record in the table&lt;/em&gt; until it finds the correct record. &lt;/p&gt;

&lt;p&gt;What many developers miss - which will help with later tips - is &lt;em&gt;the same principle applies to all queries including &lt;code&gt;delete&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Consider these two statements:&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="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both of these statements have to &lt;strong&gt;first find the row that has &lt;code&gt;id&lt;/code&gt; 5.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;id&lt;/code&gt; is a primary key, it's already indexed. &lt;/p&gt;

&lt;p&gt;The kicker is this: &lt;em&gt;if there wasn't an index on id then these statements would have to loop through the entire table until it finds the row with &lt;code&gt;id&lt;/code&gt; 5.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Without an index, the &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt; would be &lt;em&gt;really slow&lt;/em&gt; on any non-trivially sized table.&lt;/p&gt;

&lt;p&gt;This idea is foundational to the following tips and will unlock your database skills because it applies to all types of queries and statements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selecting a subset of records
&lt;/h3&gt;

&lt;p&gt;Consider this query:&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="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like querying against the &lt;code&gt;id&lt;/code&gt; column, creating an index on the &lt;code&gt;user_id&lt;/code&gt; column will greatly speed this up in any non-trivially sized table.&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="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;my_index&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;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember the principle that indexes don't just apply to &lt;code&gt;select&lt;/code&gt; queries but to &lt;em&gt;any time that the query engine needs to find records?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This index would apply to the following &lt;code&gt;delete&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt; statements:&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="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: This is why answering the question "should I add an index" is not always straightforward. The answer depends on whether use said column in &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt; or &lt;code&gt;select&lt;/code&gt; statements. As you'll read later, impacts on write performance, disk usage, etc. also impact this decision!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditions On Multiple Columns
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;work_remote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; 
&lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this query, we're looking for all users that are owned by a specific SaaS tenant where each user is working remotely.&lt;/p&gt;

&lt;p&gt;Now that the query is filtering by more than one column the possibilities of what you can/should do become more "interesting".&lt;/p&gt;

&lt;p&gt;What are your options?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No indexes&lt;/li&gt;
&lt;li&gt;Create an index on &lt;code&gt;tenant_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create an index on &lt;code&gt;work_remote&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create both of the indexes above &lt;/li&gt;
&lt;li&gt;Create a multi-column index on (&lt;code&gt;tenant_id&lt;/code&gt;, &lt;code&gt;work_remote&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create a multi-column index on (&lt;code&gt;work_remote&lt;/code&gt;, &lt;code&gt;tenant_id&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a lot of options! And it grows even more if we add another column to the mix!&lt;/p&gt;

&lt;p&gt;This is where the trade-offs of using indexes play into your decision. Here are some general guidelines that might be helpful as a starting point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is your table only few thousand records in length?

&lt;ul&gt;
&lt;li&gt;It might be best to not use any indexes&lt;/li&gt;
&lt;li&gt;Or just index &lt;code&gt;tenant_id&lt;/code&gt; as a high-value lower impact change and monitor&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Do you have many other queries that use these columns in different combinations?

&lt;ul&gt;
&lt;li&gt;Might be best to consider using two separate indexes&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Do you need this query to perform &lt;em&gt;as well as it possibly could (and these tables are more than a few thousand records)?&lt;/em&gt; 

&lt;ul&gt;
&lt;li&gt;Create an index with the order (tenant_id, work_remote)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: Some databases are really poor at applying multiple indexes in the same query. It's good to benchmark these things vs. assuming things and understand what indexing optimizations your database supports. For example, PostgreSQL &lt;a href="https://www.postgresql.org/docs/16/indexes-bitmap-scans.html#INDEXES-BITMAP-SCANS" rel="noopener noreferrer"&gt;supports using bitmap index scans&lt;/a&gt; where many separate indexes can be combined and used by the same query. Not every database supports this.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Order Matters?
&lt;/h4&gt;

&lt;p&gt;The order of columns defined in a multi-column index make all the difference.&lt;/p&gt;

&lt;p&gt;That is, order matters in a b-tree index. For other specialized index types, it might not.&lt;/p&gt;

&lt;p&gt;Why? The purpose of an index is to eliminate as many non-matching records as quickly as possible. &lt;em&gt;You always want the first column in an index to be the most specific of the columns.&lt;/em&gt; This will whittle down the pool of possible records that match your criteria faster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feapwdwh2uobjca4xhstb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feapwdwh2uobjca4xhstb.png" alt="btree structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the image above, we want to whittle down our possible records as quickly as possible. &lt;/p&gt;

&lt;p&gt;The first option with &lt;code&gt;work_remote&lt;/code&gt; as the leading indexed column doesn't really help us. Best case, we still have 1 million records left to search through. We've only split the possible records to search by two.&lt;/p&gt;

&lt;p&gt;The second option using &lt;code&gt;tenant_id&lt;/code&gt; will more quickly reduce the number of possible records. We're able to quickly grab a very selective subset of all the records: only the records belonging to &lt;code&gt;tenant_id&lt;/code&gt; 5 will be fetched from the table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Index columns used in joins
&lt;/h3&gt;

&lt;p&gt;Usually, queries with &lt;code&gt;join&lt;/code&gt; will &lt;code&gt;join&lt;/code&gt; on columns that are foreign keys.&lt;/p&gt;

&lt;p&gt;Consider this query:&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="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;inner&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;addresses&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a silly query to run because it grabs data for every single user.&lt;/p&gt;

&lt;p&gt;What does the query engine need to do?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loop through every record in the users table.&lt;/li&gt;
&lt;li&gt;For each &lt;code&gt;user&lt;/code&gt; record, find the &lt;code&gt;address&lt;/code&gt; record who's &lt;code&gt;user_id&lt;/code&gt; is the same value as the &lt;code&gt;id&lt;/code&gt; column on the users table.&lt;/li&gt;
&lt;li&gt;Return the data to the database client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For no. 2 above, each time the query engine needs to find a specific record in the &lt;code&gt;address&lt;/code&gt; table it will loop through all the records in that table.&lt;/p&gt;

&lt;p&gt;Let me repeat: the query will loop through the entire &lt;code&gt;addresses&lt;/code&gt; table &lt;em&gt;for every single user record.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If both tables are large: &lt;em&gt;this could take a while&lt;/em&gt;. You'll have a really slow query.&lt;/p&gt;

&lt;h4&gt;
  
  
  Joins With Foreign Keys
&lt;/h4&gt;

&lt;p&gt;Luckily for you, some databases will automatically create an index on all foreign keys that you create! So no worries, right?&lt;/p&gt;

&lt;p&gt;Oh. But some databases won't do this. &lt;/p&gt;

&lt;p&gt;We're all looking at you &lt;a href="https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-FK" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;... it is often a good idea to index the referencing columns too. Because this is not always needed, and there are many choices available on how to index, the declaration of a foreign key constraint does not automatically create an index on the referencing columns.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oh ya. &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/tables/primary-and-foreign-key-constraints?view=sql-server-ver16#indexes-on-foreign-key-constraints?wt.mc_id=DT-MVP-5003843" rel="noopener noreferrer"&gt;SQL Server too&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt; Creating a foreign key constraint doesn't automatically create a corresponding index.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In contrast, &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/create-table-foreign-keys.html" rel="noopener noreferrer"&gt;MySQL will create an index on a foreign key column for you&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Such an index is created on the referencing table automatically if it does not exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5&gt;
  
  
  Foreign Key Joins In Update And Delete Statements
&lt;/h5&gt;

&lt;p&gt;Again, this idea of adding an index to foreign keys affects &lt;code&gt;update&lt;/code&gt; and &lt;code&gt;delete&lt;/code&gt; statements. Consider the following:&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="k"&gt;delete&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;addresses&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;inner&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;work_remote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This statement would delete all addresses where the corresponding user works remotely. If there's no index on foreign key column &lt;code&gt;addresses.user_id&lt;/code&gt; then that table would need to do a full scan (e.g. full loop through the table) for every single user record that works remotely.&lt;/p&gt;

&lt;p&gt;In PostgreSQL and SQL Server, you'd definitely want to be aware of this.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: It's also worth mentioning that some ORMs will automatically create an index for all foreign keys you create, like &lt;a href="https://learn.microsoft.com/en-us/ef/core/modeling/relationships/foreign-and-principal-keys#indexes-for-foreign-keys?wt.mc_id=DT-MVP-5003843" rel="noopener noreferrer"&gt;Entity Framework!&lt;/a&gt; I pays to understand the tools that you're working with 🥲.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Joins Using Non-Foreign Key Columns
&lt;/h4&gt;

&lt;p&gt;I'd say this is rare in many SaaS applications. But, it does happen in analytical queries, unstructured data, etc.&lt;/p&gt;

&lt;p&gt;Consider the following:&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="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;account_codes&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;
&lt;span class="k"&gt;inner&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;imported_codes&lt;/span&gt; &lt;span class="n"&gt;imported&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imported&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;imported&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine that you have some account codes (whatever that is) in our system. We've also imported codes from a 3rd party system that doesn't match up with our data by any IDs.&lt;/p&gt;

&lt;p&gt;In this join, you could (&lt;em&gt;most likely&lt;/em&gt;) significantly speed up the query by indexing both sides:&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="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;account_codes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;imported_codes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cascading Operations
&lt;/h3&gt;

&lt;p&gt;Consider this schema:&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="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&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;id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;placed_at&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="k"&gt;zone&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;user_id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;users&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;on&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;cascade&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's focus on the foreign key definition. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;on delete cascade&lt;/code&gt; means that whenever a user record is deleted any referencing records in &lt;code&gt;orders&lt;/code&gt; will automatically be deleted too.&lt;/p&gt;

&lt;p&gt;Imagine &lt;code&gt;user&lt;/code&gt; with &lt;code&gt;id&lt;/code&gt; 5 is deleted. The database has to find all orders &lt;code&gt;where user_id = 5&lt;/code&gt; to delete them.&lt;/p&gt;

&lt;p&gt;If there's no index on &lt;code&gt;user_id&lt;/code&gt; then this will require a full table scan. If you tried to delete many user records, then this means &lt;em&gt;many full tables scans&lt;/em&gt; against the &lt;code&gt;orders&lt;/code&gt; table!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: If you ever have a page in your application that deletes records and it takes longer than expected - it could be there are some cascading deletes that might benefit from some indexes!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Index Trade-Offs
&lt;/h2&gt;

&lt;p&gt;Indexes aren't magic. As with all things in software development: &lt;strong&gt;there are trade-offs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's shoot through some of the important trade-offs:&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;p&gt;Indexes use a lot of disk space. In many cases, one index can use the same space as the table it's indexing. Yup.&lt;/p&gt;

&lt;p&gt;Once you start using multi-column indexes and specialized index types then your indexes can be even larger than your tables!&lt;/p&gt;

&lt;p&gt;The benefit is that you can significantly speed up certain queries or operations like deleting and updating records. But if you aren't careful and considerate of this point, you can quickly cause your database size to expand significantly.&lt;/p&gt;

&lt;p&gt;This can lead to slow backups, slow performance due to memory pressure, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqsrhhz6exma9sglrda9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqsrhhz6exma9sglrda9.png" alt="database disk storage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory Pressure
&lt;/h3&gt;

&lt;p&gt;Databases will load the most often used objects into available RAM to speed things up (tables, indexes, etc.). However, if you create too many indexes then the amount of data that your database needs to keep in memory can start to exceed available RAM.&lt;/p&gt;

&lt;p&gt;Not entire indexes are kept in memory - only the most often used disk pages, to be specific. But the point still applies: you're forcing the database to require storing too much "stuff" in RAM. This can start to cause severe issues if you're not careful!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlf1vo04t33bd1006lgo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlf1vo04t33bd1006lgo.png" alt="database message pressure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Write Performance
&lt;/h3&gt;

&lt;p&gt;Indexes have to be in sync with the table that they index. This is one way that relational databases keep the data consistent and referential integrity in place.&lt;/p&gt;

&lt;p&gt;Every time a new record is inserted, updated or deleted, the corresponding index entries across all indexes have to be modified/added.&lt;/p&gt;

&lt;p&gt;The more indexes you have the more work has to be done to &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt; or &lt;code&gt;delete&lt;/code&gt; a record.&lt;/p&gt;

&lt;p&gt;This is why guidance around bulk loading data usually recommends that you turn off all indexes, load your data and then turn the indexes back on again.&lt;/p&gt;

&lt;p&gt;If you have tables where you need data modifications to be super fast then consider keeping your usage of indexes at a minimum.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faf2ldvj4s7v6xubg12go.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faf2ldvj4s7v6xubg12go.png" alt="database index impact on write performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  More Index Types
&lt;/h3&gt;

&lt;p&gt;You don't need to understand how all the different index types work. &lt;/p&gt;

&lt;p&gt;However, it's really useful to have a cursory knowledge of what problems specialized indexes solve. If you come across these problems then you'll remember that there is a solution. &lt;em&gt;Then&lt;/em&gt;, you can get into the specifics.&lt;/p&gt;

&lt;p&gt;Here's a quick rundown of some more specialized indexes available in PostgreSQL and what problems they solve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.postgresql.org/docs/current/hash-intro.html" rel="noopener noreferrer"&gt;Hash index&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Fast equality checks. Really useful for scanning against large unique string/text values.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create index user_email on users using hash (email);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://www.postgresql.org/docs/current/gin-intro.html" rel="noopener noreferrer"&gt;GIN index&lt;/a&gt; (Generalized Inverted Index)

&lt;ul&gt;
&lt;li&gt;Useful for full-text search and indexing JSON data.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create index gin_docs on documents using gin (to_tsvector('english', text));&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://www.postgresql.org/docs/current/indexes-expressional.html" rel="noopener noreferrer"&gt;Expression index&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Supports indexing usage of functions within queries&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create index lower_email on users (lower(email));&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://www.postgresql.org/docs/current/indexes-index-only-scans.html" rel="noopener noreferrer"&gt;Covering index&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Supports squeezing as much performance as possible out of very specific queries.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create index covering_orders on orders using btree (user_id, ordered_at desc) include (special_instructions, coupon_code);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://www.postgresql.org/docs/current/bloom.html" rel="noopener noreferrer"&gt;Bloom index&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;You know those pesky reports that have a million different filters the user can choose? Ya - this is for that 🤣.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create index my_bloom on report_data using bloom (index1, index2, index3, index4, index5);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://www.postgresql.org/docs/16/brin-intro.html" rel="noopener noreferrer"&gt;BRIN index &lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Useful when you have a table that is append-only&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create index my_brin on facts using brin (created_at);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Final Tip
&lt;/h2&gt;

&lt;p&gt;There's much to say about standards and how they give you transferable knowledge as a developer.&lt;/p&gt;

&lt;p&gt;But, the fact is: &lt;em&gt;learning in-depth details about how your specific database works does have a massive impact on how you think, design, code and troubleshoot databases.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You don't need to become a database expert. But... I mean... &lt;em&gt;&lt;strong&gt;all of your slow pages, reports, slow backups, large disk usage, downtime, etc. are caused by your database!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For real applications with a legit user base: databases are important. Critical.&lt;/p&gt;

&lt;p&gt;That doesn't mean you can't get by with a general purpose database using general design and coding guidelines. But if you ever land those big customers that push your application - &lt;em&gt;your database is going to be the bottleneck.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Learn how it works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A great place to start are the PostgreSQL official docs. It's some of the best written documentation in the industry. The docs really get into the nitty-gritty and help you to understand what's happening at a lower level. Much of this knowledge, in my experience, is transferable.&lt;/p&gt;

&lt;p&gt;Start with the documentation on &lt;a href="https://www.postgresql.org/docs/current/indexes.html" rel="noopener noreferrer"&gt;indexes&lt;/a&gt;. Take what you've learned here and dig into some more!&lt;/p&gt;

</description>
      <category>database</category>
      <category>webdev</category>
      <category>postgres</category>
      <category>performance</category>
    </item>
    <item>
      <title>15 Common .NET &amp; C# Blunders Based On My Experience</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Thu, 01 Aug 2024 17:37:17 +0000</pubDate>
      <link>https://forem.com/jamesmh/15-common-net-c-blunders-based-on-my-experience-2lg4</link>
      <guid>https://forem.com/jamesmh/15-common-net-c-blunders-based-on-my-experience-2lg4</guid>
      <description>&lt;p&gt;I’ve been using C# and .NET for over a decade now 🙄. I’ve worked with developers professionally in private organizations and out "in the open" within the open-source world.&lt;/p&gt;

&lt;p&gt;I thought I would compile some of the most common ".NET blunders" that I personally have seen time and time again.&lt;/p&gt;

&lt;p&gt;And just to make it clear: &lt;em&gt;these are all things I had to learn too and keep up to date on too!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  #1: Async/Await…
&lt;/h2&gt;

&lt;p&gt;I’ll start by saying that I see C# &lt;code&gt;async&lt;/code&gt; misunderstood and misused &lt;em&gt;all the time&lt;/em&gt;. I’ve seen very senior, experienced, skilled and knowledgeable C# developers do it. I’ve seen myself do it too!&lt;/p&gt;

&lt;p&gt;Based on my experience, I say that C# does not lead developers into the "pit of success" on asynchronous tooling and syntax.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;C# has a language-level asynchronous programming model, which allows for easily writing asynchronous code without having to juggle callbacks or conform to a library that supports asynchrony.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sure.&lt;/p&gt;

&lt;p&gt;There are great resources on this topic &lt;a href="https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#asynchronous-programming" rel="noopener noreferrer"&gt;2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I think the fact that these exist highlight that it’s not so straightforward.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Take a look at the comments on this &lt;a href="https://news.ycombinator.com/item?id=40208876" rel="noopener noreferrer"&gt;recent HackerNews thread&lt;/a&gt; about C# &lt;code&gt;async/await&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In any event, here’s a quick-fire list of specific questions I’ve often had from other developers when digging into C# asynchronous code. I’ll try to address each one very briefly.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;async void&lt;/code&gt;: doesn’t that just support asynchronous code and the caller can "fire-and-forget"?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#async-void" rel="noopener noreferrer"&gt;No. It’s really bad. Never do it.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are all sorts of issues. An exception in an &lt;code&gt;async void&lt;/code&gt; method can crash your entire application, for example.&lt;/p&gt;




&lt;h3&gt;
  
  
  Can’t I just put a synchronous call inside &lt;code&gt;Task.Run&lt;/code&gt; to make it asynchronous?
&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;myVar&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;Task&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;span class="k"&gt;async&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="nf"&gt;doSynchronousThing&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;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&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. It doesn’t.&lt;/p&gt;

&lt;p&gt;This doesn’t do any asynchronous work and could force the use of an extra thread depending on your context, system load, etc.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: This code will usually trigger a warning in your IDE because there’s no &lt;code&gt;await&lt;/code&gt; keyword used . But you can do this, and I keep seeing it.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Can’t I use &lt;code&gt;Task.Run&lt;/code&gt; to make really performant parallelized code?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Again, asynchronous code is not the same as parallelized code.&lt;/p&gt;

&lt;p&gt;A bit of a tangent maybe, but related… I’ve seen the following code many times:&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;myTasks&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;myTasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&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;.&lt;/span&gt;&lt;span class="nf"&gt;Run&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="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;doSynchronousThing1&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="n"&gt;myTasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&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;.&lt;/span&gt;&lt;span class="nf"&gt;Run&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="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;doSynchronousThing2&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;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myTasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few issues here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Notice that inside the delegate for each &lt;code&gt;Task.Run&lt;/code&gt; we aren’t returning &lt;code&gt;Task.CompletedTask&lt;/code&gt;. This makes that delegate’s signature &lt;code&gt;async void&lt;/code&gt; (see point above on &lt;code&gt;async void&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;This has the same issue as the point above about using &lt;code&gt;Task.Run&lt;/code&gt; to force asynchronicity (e.g. it doesn’t).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the end, this code doesn’t do any asynchronous work and can cause some thread context switching (e.g. there’s unnecessary overhead).&lt;/p&gt;




&lt;h3&gt;
  
  
  If I don’t &lt;code&gt;await&lt;/code&gt; a &lt;code&gt;Task&lt;/code&gt; doesn’t that make my method way more performant?
&lt;/h3&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="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;DoAsynchronousThing&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;myTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;someOtherAsyncStuff&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;myTask&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;To keep this simple – &lt;em&gt;just use &lt;code&gt;async&lt;/code&gt; on all your asynchronous methods.&lt;/em&gt; You benefit more than trying to be smart with a feature that developers keep getting wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#prefer-asyncawait-over-directly-returning-task" rel="noopener noreferrer"&gt;David Fowler’s Async Guidance:&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are benefits to using the async/await keyword instead of directly returning the Task…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Note: Depending on where/how you do this, you might get some IDE warnings. Heed the warnings.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Isn’t &lt;code&gt;Task.Factory.StartNew&lt;/code&gt; the same as &lt;code&gt;Task.Run&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Create a new empty .NET console application using &lt;code&gt;dotnet new console my-test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;Program.cs&lt;/code&gt; with the following:&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;using&lt;/span&gt; &lt;span class="nn"&gt;System.Diagnostics&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;watch&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;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&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;tasks&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="k"&gt;for&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;i&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&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;.&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;StartNew&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="p"&gt;=&amp;gt;&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;DoDelay&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"The program is closing... it took &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ms to run!!"&lt;/span&gt;&lt;span class="p"&gt;);&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;DoDelay&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;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10000&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;How long will this program take to execute: more than 10 seconds? &lt;strong&gt;Try it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Spoiler: It takes &lt;strong&gt;milliseconds&lt;/strong&gt; to run. Did you expect that?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here are a couple articles addressing this in detail (&lt;a href="https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/" rel="noopener noreferrer"&gt;2&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  #2: Parallel Processing While Handling HTTP Requests
&lt;/h2&gt;

&lt;p&gt;Here’s a silly example of an HTTP endpoint to show what I’m getting at:&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;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;PostUpdateUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;existingNameFound&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&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;existingNames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetExistingUserNames&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existingNames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;existingNameFound&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="c1"&gt;// Do something with existingNameFound &amp;gt; 0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using an asynchronous model to process HTTP requests &lt;em&gt;is not the same&lt;/em&gt; as parallel processing.&lt;/p&gt;

&lt;p&gt;With asynchronous code, you are trying to make your .NET threads do as little work as possible. For web applications specifically, this will keep your applications responsive, capable to serve many requests concurrently and therefore scale well.&lt;/p&gt;

&lt;p&gt;Parallel code is the &lt;strong&gt;opposite&lt;/strong&gt;: &lt;em&gt;it uses and keeps a hold on threads.&lt;/em&gt; This is to do CPU-intensive work like heavy calculations. This is usually performed using the &lt;code&gt;Parallel&lt;/code&gt; static class.&lt;/p&gt;

&lt;p&gt;I’ve seen parallel code in web applications being performed during an HTTP request. Usually, the code performing these heavy calculations wasn’t inside MVC controllers but farther down the chain of execution.&lt;/p&gt;

&lt;p&gt;This is bad because parallel processing hogs threads that otherwise should be used sparingly to respond to HTTP requests.&lt;/p&gt;

&lt;p&gt;If you need to use parallel processing, &lt;strong&gt;don’t do it during an HTTP request.&lt;/strong&gt; This will lead to reducing the scalability and throughput of your overall web server. It could also lead to increasing overall memory pressure on your web process/server too.&lt;/p&gt;

&lt;h2&gt;
  
  
  #3: Trying To Self-Manage Database Connections
&lt;/h2&gt;

&lt;p&gt;Have you ever seen code where an &lt;code&gt;SqlConnection&lt;/code&gt; is passed around as an argument and then used like this?&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;SomeData&lt;/span&gt; &lt;span class="nf"&gt;DoDatabaseThing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SqlConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;ConnectionState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Do a bunch of stuff, calling more methods&lt;/span&gt;
    &lt;span class="c1"&gt;// and passing the connection as an argument...&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;ConnectionState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Closed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Return data.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some say that being defensive and making sure you manage your database connections this way is "good". In my experience, this type of code is an easy source of bugs.&lt;/p&gt;

&lt;p&gt;One time, an application I worked on was using this pattern. I had mentioned a few times that this kind of code could lead to issues if we aren’t careful…&lt;/p&gt;

&lt;p&gt;One day, we found that after a number of hours our web server would crash. We had to manually restart the web server in production every couple of hours… Eventually, I found the source:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The developer in question introduced database code that caused C# code to throw an exception – before trying to close the connection. That exception was then silently caught up the stack! 💣&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;E.g. The connection was never closed and caused a memory/connection leak.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The more you try to be "smart" with database connections, the more chances you have to mess things up.&lt;/p&gt;

&lt;p&gt;There are 2 things that come to mind on this topic :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Always put your &lt;code&gt;SqlConnection&lt;/code&gt; inside of a &lt;code&gt;using&lt;/code&gt; block. It will be automatically closed once it’s out of scope.&lt;/li&gt;
&lt;li&gt;Under the covers, &lt;code&gt;SqlConnection&lt;/code&gt; is really a &lt;a href="https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-connection-pooling" rel="noopener noreferrer"&gt;pool of connections&lt;/a&gt;. It’s already managed for you so you’re not gaining anything trying to manage things yourself.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  #4: HttpClient
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;HttpClient&lt;/code&gt; is a class that does what you think it does. However, this class is not as straightforward as it could (should?) be.&lt;/p&gt;

&lt;p&gt;The normal thinking in C# is that any class which implements &lt;code&gt;IDisposable&lt;/code&gt; should be used within a &lt;code&gt;using&lt;/code&gt; block. Since &lt;code&gt;HttpClient&lt;/code&gt; isn’t disposable, we should be able to create a new instance any time we need to use it, right?&lt;/p&gt;

&lt;p&gt;If you check out the current example of using &lt;code&gt;HttpClient&lt;/code&gt; in the &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-8.0#examples" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;, you’ll see this comment:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;HttpClient&lt;/code&gt; is intended to be instantiated once per application, rather than per-use. See Remarks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the "Remarks" section, there’s a link to &lt;a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-net-http-httpclient" rel="noopener noreferrer"&gt;this article&lt;/a&gt; with more information about how this class works.&lt;/p&gt;

&lt;p&gt;I’ve seen systems that had weird network issues and socket exceptions because of misusing &lt;code&gt;HttpClient&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The general advice with using &lt;code&gt;HttpClient&lt;/code&gt; is to &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory" rel="noopener noreferrer"&gt;use IHttpClientFactory to "create" instances for you.&lt;/a&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="n"&gt;MyClass&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;IHttpClientFactory&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;public&lt;/span&gt; &lt;span class="nf"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHttpClientFactory&lt;/span&gt; &lt;span class="n"&gt;factory&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="n"&gt;factory&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;DoRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&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;CreateHttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Use `client` now to perform http requests.&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;
  
  
  #5: Calling ToList() Unnecessarily
&lt;/h2&gt;

&lt;p&gt;I’ve seen this one a lot. In most situations it’s not a big deal and the side-effects are not felt.&lt;/p&gt;

&lt;p&gt;This can happen for many reasons. For example, a developer doesn’t realize that they’ve already called &lt;code&gt;ToList()&lt;/code&gt; before from some other method acting on the same collection.&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;users&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;GetUsers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;Thing1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;Thing2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&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;Thing1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;users&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;list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Do something stuff on list.&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;Thing2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;users&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;list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Do something stuff on list.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, this is usually not an issue… until it is.&lt;/p&gt;

&lt;p&gt;When working with larger collections, performance sensitive applications or sensitive hot paths of an application can be negatively affected.&lt;/p&gt;

&lt;p&gt;Calling &lt;code&gt;ToList()&lt;/code&gt; will &lt;em&gt;cause extra memory allocations&lt;/em&gt;. With large collections or when creating a large numbers of collections overall (or both), this can lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using more memory than necessary&lt;/li&gt;
&lt;li&gt;Allocating memory is CPU intensive – so it uses extra CPU resources&lt;/li&gt;
&lt;li&gt;Additional GC pressure – which can cause large slow downs and pauses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One example from my experience was improving the performance of an application that processed/aggregated millions of data points as quickly as possible. By finding a couple of places where I could remove redundant calls to &lt;code&gt;ToList()&lt;/code&gt;, I decreased processing time by 10%.&lt;/p&gt;

&lt;p&gt;In this case, the processes could take 6 days to run. Just this one change would have an improvement of a half-day.&lt;/p&gt;

&lt;p&gt;Again, this isn’t necessarily about reducing memory usage – it’s about conserving CPU cycles used to allocate new memory and how often the .NET garbage collector has to run.&lt;/p&gt;

&lt;p&gt;If you want HTTP requests that are blazing fast too, then this is one thing to keep an eye out for!&lt;/p&gt;

&lt;h2&gt;
  
  
  #6: Ignoring Database Fundamentals Because The ORM Will "Take Care Of It"
&lt;/h2&gt;

&lt;p&gt;ORMs are great. They can make working with the 90% of database usage in application code straightforward and easy. 90% of database migrations that you’ll need can be autogenerated too!&lt;/p&gt;

&lt;p&gt;But then you have some aspect of your system that needs something special:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A reporting query that used to be fast but that now takes down the database when running with that 1 massive customer&lt;/li&gt;
&lt;li&gt;That batch insert/update code path that used to be fine but is now abysmally slow&lt;/li&gt;
&lt;li&gt;A set of dashboard queries that customers have been complaining about&lt;/li&gt;
&lt;li&gt;That new full-text search feature customers have been asking for&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What do you do? The more you know about databases – the better.&lt;/p&gt;

&lt;p&gt;There’s a place for simplicity, which ORMs are great at. But some situations call for more nuanced solutions.&lt;/p&gt;

&lt;p&gt;Here are some areas where it might help you to have some extra knowledge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/sql-server-index-design-guide?view=sql-server-ver16" rel="noopener noreferrer"&gt;Indexing:&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Which tables? Which columns? Clustered vs. non-clustered? B-tree vs. GIN? Etc.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Beyond basic SQL operators:

&lt;ul&gt;
&lt;li&gt;Merge&lt;/li&gt;
&lt;li&gt;Cross Apply&lt;/li&gt;
&lt;li&gt;CTEs&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/storage-hot.html" rel="noopener noreferrer"&gt;PostgreSQL Hot Updates&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/textsearch-intro.html" rel="noopener noreferrer"&gt;Full-text Search&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;Query performance analysis

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/performance/monitoring-performance-by-using-the-query-store?view=sql-server-ver16" rel="noopener noreferrer"&gt;SQL Server Query Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cybertec-postgresql.com/en/how-to-interpret-postgresql-explain-analyze-output/" rel="noopener noreferrer"&gt;Explain Analyze&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  #7: Long-running Or Overnight Jobs That Keep Running Out Of Memory!
&lt;/h2&gt;

&lt;p&gt;I’ve seen this a number of times. It’s not something complicated, but a mistake usually made when the dataset being used in production is small. Then, over time it grows larger and larger and bad data access patterns begin to cause problems.&lt;/p&gt;

&lt;p&gt;Imagine an overnight processing job that grabs all the records from a database table and processes them all in memory.&lt;/p&gt;

&lt;p&gt;With 10 items to process, this might be fine. But with 10,000 items you’ll start getting memory issues.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;My advice: Paginate when dealing with a collection of items where you aren’t sure that it’s going to remain the same size.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  #8: Creating A Ton Of Different Separate Projects For One Business/Domain Context
&lt;/h2&gt;

&lt;p&gt;I’ve written about this a few times (&lt;a href="https://www.jamesmichaelhickey.com/how-to-structure-your-dot-net-solutions-design-and-trade-offs/" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://www.jamesmichaelhickey.com/clean-architecture/" rel="noopener noreferrer"&gt;2&lt;/a&gt;, &lt;a href="https://builtwithdot.net/blog/changing-how-your-code-is-organized-could-speed-development-from-weeks-to-days" rel="noopener noreferrer"&gt;3&lt;/a&gt;). I’m not the biggest fan of clean architecture. I think many of the principles found in the typical presentation of it are solid (no pun intended!), but I tend to &lt;a href="https://www.jamesmichaelhickey.com/clean-architecture/" rel="noopener noreferrer"&gt;disagree with the application of those principles.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In short, we software developers like to complicate things because it’s "more SOLID" or "more X".&lt;/p&gt;

&lt;p&gt;For me simplicity (usually) wins.&lt;/p&gt;

&lt;p&gt;Do you need 5 different .NET projects like &lt;code&gt;Web&lt;/code&gt;, &lt;code&gt;Core&lt;/code&gt;, &lt;code&gt;Infrastructure&lt;/code&gt;, &lt;code&gt;DataAccess&lt;/code&gt;, &lt;code&gt;Shared&lt;/code&gt;, etc.?&lt;/p&gt;

&lt;p&gt;What if instead we focus how our folders &amp;amp; projects are structured by the capabilities of our business/system?&lt;/p&gt;

&lt;p&gt;Incoming heresy: Maybe it’s &lt;strong&gt;&lt;em&gt;okay&lt;/em&gt;&lt;/strong&gt; to put database related code inside the same folder as business logic code? They are still in separate files. But now you don’t need to context switch to find your domain code vs. database code. &lt;em&gt;You can still use interfaces&lt;/em&gt;. No one is stopping you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FjQ03KtF.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FjQ03KtF.png" alt="vertical slices architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For my more nuanced thoughts, check out &lt;a href="https://www.jamesmichaelhickey.com/how-to-structure-your-dot-net-solutions-design-and-trade-offs/" rel="noopener noreferrer"&gt;".NET Architecture: How To Structure Your Solutions."&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  #9: Allowing Unlimited Network Requests To Your APIs
&lt;/h2&gt;

&lt;p&gt;If you build any HTTP API: it will be abused. Sonner or later. Even by those within the same organization!&lt;/p&gt;

&lt;p&gt;It could be a rogue/poorly written script or even a malicious attacker.&lt;/p&gt;

&lt;p&gt;Not protecting your API with rate limits leaves the door open for unnecessary outages.&lt;/p&gt;

&lt;p&gt;Again, &lt;em&gt;don’t trust that consumers will not abuse your API.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For modern .NET developers, there are &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-8.0" rel="noopener noreferrer"&gt;newer built-in tools to easily add rate limiting to your APIs.&lt;/a&gt; Use them!&lt;/p&gt;

&lt;h2&gt;
  
  
  #10: Using &lt;code&gt;PostAsync&lt;/code&gt; Instead Of &lt;code&gt;PostAsJsonAsync&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The newer &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.net.http.json.httpclientjsonextensions.postasjsonasync?view=net-8.0" rel="noopener noreferrer"&gt;&lt;code&gt;PostAsJsonAsync&lt;/code&gt;&lt;/a&gt; extension method on &lt;code&gt;HttpClient&lt;/code&gt; should be your "go to" for making typical HTTP POST requests.&lt;/p&gt;

&lt;p&gt;Behind the scenes, this method uses some fancy memory pooling, etc. to make it’s memory allocations minimal.&lt;/p&gt;

&lt;p&gt;A much better performance profile can mean things like using a smaller cloud instance size – which reduces cost and complains from the DevOps people 🤣. If you are building a sharable library, then your code is less of a performance burden on the consumer code.&lt;/p&gt;

&lt;p&gt;All-in-all, it’s a quick win!&lt;/p&gt;

&lt;h2&gt;
  
  
  #11: Building Proprietary "Frameworks" And Wrappers Around Built-in .NET Functionality
&lt;/h2&gt;

&lt;p&gt;Anytime I see the name "framework" inside source code that’s not part of .NET I raise an eyebrow.&lt;/p&gt;

&lt;p&gt;I’ve seen so many custom frameworks: custom UI frameworks, custom HTTP routing, custom HTML renderers, custom data access frameworks, etc.&lt;/p&gt;

&lt;p&gt;Internal frameworks that make working with HTTP requests, routing, data binders, etc. "easier" are bound to cause awkward situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The framework wasn’t supposed to be used everywhere. But now it is.&lt;/li&gt;
&lt;li&gt;The framework overrides built-in .NET functionality, leading to a net restriction of available functionality instead of a net increase.&lt;/li&gt;
&lt;li&gt;New (to the org) developers have to (re)learn all this stuff&lt;/li&gt;
&lt;li&gt;There’s usually no documentation. If there is, it’s probably terrible.&lt;/li&gt;
&lt;li&gt;What if the 1 person who wrote most of the framework leaves the company?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my experience, most things that deviate from a standard .NET functionality that’s existed for years is (usually) a can of worms. Or a pandora’s box. Something like that.&lt;/p&gt;

&lt;h2&gt;
  
  
  #12: Using Stored Procedures Everywhere
&lt;/h2&gt;

&lt;p&gt;Back in the day (like over 10 years ago…) stored procedures were considered by many as a silver bullet.&lt;/p&gt;

&lt;p&gt;They are more secure than parameterized SQL statements from application code, more performant, easier to understand, easier to test, etc.&lt;/p&gt;

&lt;p&gt;So they said.&lt;/p&gt;

&lt;p&gt;Today, I think that the consensus (and truth?) is that none of these points are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameterized SQL statements are just as safe.&lt;/li&gt;
&lt;li&gt;99% of queries you need to do are just as performant.&lt;/li&gt;
&lt;li&gt;Stored procedures are harder to (source) version.&lt;/li&gt;
&lt;li&gt;The size and scope of stored procedures (in my experience) have the tendency to grow out of control quickly. This is because they are difficult to modularize and test.&lt;/li&gt;
&lt;li&gt;Parameterized SQL statements can be tested as part of your .NET test suite if you’ve organized things well. No need to run separate database tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://blog.codinghorror.com/who-needs-stored-procedures-anyways/" rel="noopener noreferrer"&gt;This is a great article on the topic.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  #13: Dogmatically Never Using Stored Procedures
&lt;/h2&gt;

&lt;p&gt;With all things in our industry, it’s easy to be dogmatic. This article is somewhat dogmatic 🤔.&lt;/p&gt;

&lt;p&gt;Specifically with stored procedures, &lt;em&gt;they do have a place.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In my mind, their main benefit is when you have some bulk-ish operations that you need to perform. Sending multiple SQL statements over-the-wire (like hundreds) can be slow. Especially if the database is not on the same machine as your .NET application (this is probably the case for most readers).&lt;/p&gt;

&lt;p&gt;The latency between your application and database is usually negligible. But when you are doing bulk-ish stuff, then it can be a source of bottleneck.&lt;/p&gt;

&lt;p&gt;Stored procedures have the benefit of storing the SQL statements on the database. You can send over the data you want to work on once, and then let the database do the bulk of the work. Instead of doing a loop in your application code, you put the loop in the stored procedure.&lt;/p&gt;

&lt;p&gt;You can send all your data via 1 database call, or in chunks. Either way, this can be a great use case for using a stored procedure. &lt;em&gt;I’ve seen massive improvements to critical operations with this approach, when used sparingly.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  #14: Microservices
&lt;/h2&gt;

&lt;p&gt;"Microservice all the things" sounds great.&lt;/p&gt;

&lt;p&gt;The idea of decoupling teams and having high-performing, modular and isolated services sounds great. But there can be huge costs to over-leaning into microservices.&lt;/p&gt;

&lt;p&gt;If you work in an organization who’s leaned into microservice heavily, you often find issues that appear as questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How the heck do I integrate with team X?"&lt;/li&gt;
&lt;li&gt;"There are 5 versions of service X. Which one should I use since they are all being deployed to production?"&lt;/li&gt;
&lt;li&gt;"I’ve found 5 different ways that our organization solves [insert common software design problem]? What should I do?"&lt;/li&gt;
&lt;li&gt;"Which repository is that in? Which team owns that? The guy who wrote this isn’t around anymore. Does anyone know how this thing works?"&lt;/li&gt;
&lt;li&gt;"Our UI needs data from teams X, Y and Z. Which services and APIs should I use? Oh… team Y has 5 different services that I need to stitch together? Does anyone understand the data points for any of these? &lt;em&gt;[10 meetings later]&lt;/em&gt;: So I guess there is a new service that combines these data points that no one else knew about."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these questions / musings are signs of costs: You need really good coordination, documentation, planning, etc. to pull it off well.&lt;/p&gt;

&lt;p&gt;And so… all the things I said about internal "frameworks" I think applies to microservices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"It wasn’t supposed to be used everywhere"&lt;/li&gt;
&lt;li&gt;"This service doesn’t do functionality F that the previous version of the service did…"&lt;/li&gt;
&lt;li&gt;New developers have a lot of extra stuff to learn just to get started&lt;/li&gt;
&lt;li&gt;Terrible or no documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more of my thoughts, I’d suggest checking out &lt;a href="https://www.jamesmichaelhickey.com/how-to-structure-your-dot-net-solutions-design-and-trade-offs/" rel="noopener noreferrer"&gt;".NET Architecture: How To Structure Your Solutions"&lt;/a&gt; and &lt;a href="https://www.jamesmichaelhickey.com/microservices-architecture/" rel="noopener noreferrer"&gt;"What Is A Microservice Architecture?"&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  #15: Building Your Own Background Job Scheduler
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/jamesmh/coravel" rel="noopener noreferrer"&gt;This is just a plug for my open-source project&lt;/a&gt; 🤣.&lt;/p&gt;

&lt;p&gt;But, there are costs to using open-source projects that I find developers rarely consider when they add a new library to their projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No guarantee of support&lt;/li&gt;
&lt;li&gt;Using more libraries expands potential security profile&lt;/li&gt;
&lt;li&gt;Poorly abstracted code can cause your own code to introduce hacks or poor design patterns to fit the library into your code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We’re all still learning. There’s always more to learn. But, it’s okay to not learn something new every single day.&lt;/p&gt;

&lt;p&gt;More important than learning everyday, I think, is to &lt;strong&gt;learn from our mistakes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is all.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>webdev</category>
    </item>
    <item>
      <title>High-Performance .NET CRON Jobs</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Tue, 16 Jul 2024 14:54:23 +0000</pubDate>
      <link>https://forem.com/jamesmh/high-performance-net-cron-jobs-3k8j</link>
      <guid>https://forem.com/jamesmh/high-performance-net-cron-jobs-3k8j</guid>
      <description>&lt;p&gt;CRON jobs are a staple for many software systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nightly reporting jobs&lt;/li&gt;
&lt;li&gt;Processing a backlog of queued long-running work&lt;/li&gt;
&lt;li&gt;Kicking off database clean-up scripts&lt;/li&gt;
&lt;li&gt;Periodic calculations of customer subscription renewals and payments&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See a recent article by Slack, for example, where they talk about their own CRON setup:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Over the years, both the number of cron scripts and the amount of data these scripts process have increased. While generally these cron scripts executed as expected, over time the reliability of their execution has occasionally faltered, and maintaining and scaling their execution environment became increasingly burdensome.&lt;br&gt;
&lt;a href="https://slack.engineering/executing-cron-scripts-reliably-at-scale/" rel="noopener noreferrer"&gt;https://slack.engineering/executing-cron-scripts-reliably-at-scale/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the .NET ecosystem, there are a few great libraries for scheduling or queuing background work. I created &lt;a href="https://github.com/jamesmh/coravel" rel="noopener noreferrer"&gt;Coravel&lt;/a&gt; as an easy way to build .NET applications with more advanced web application features. But it's mostly known as a background job scheduling library.&lt;/p&gt;

&lt;p&gt;I thought it would be fun to play around with the idea of building a basic CRON job system and progressively building it into a more high-performance CRON job processing system.&lt;/p&gt;

&lt;p&gt;We'll start by learning how to use Coravel in a simple scenario. Then, we'll further configure and leverage Coravel's features to squeeze more performance out of a single .NET process. Finally, you'll learn a few advanced techniques to build a high-performance background job processing system.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: I'm using a copy of the &lt;a href="https://github.com/Microsoft/sql-server-samples/releases/tag/wide-world-importers-v1.0" rel="noopener noreferrer"&gt;Wide World Importers database&lt;/a&gt; for this exercise using an SQL Server docker container as my backing sample data.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code/Repository
&lt;/h2&gt;

&lt;p&gt;You can see the &lt;a href="https://github.com/jamesmh/high-performance-dotnet-cron-jobs" rel="noopener noreferrer"&gt;sample code repository on GitHub.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building A Basic CRON Job Process
&lt;/h2&gt;

&lt;p&gt;First, I've installed some packages like &lt;code&gt;Coravel&lt;/code&gt;, &lt;code&gt;Dapper&lt;/code&gt; and the usual stuff to get a basic .NET console application up and running.&lt;/p&gt;

&lt;p&gt;Here's &lt;code&gt;Program.cs&lt;/code&gt; from my project named &lt;code&gt;Basic&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;using&lt;/span&gt; &lt;span class="nn"&gt;Basic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Coravel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Hosting&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;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateApplicationBuilder&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="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;AddScheduler&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;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Server=127.0.0.1,1433;Database=WideWorldImporters-Standard;User Id=sa;Password=P@assword;TrustServerCertificate=True;"&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;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessAllOrdersInvocable&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;host&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;host&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;UseScheduler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduler&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;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessAllOrdersInvocable&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;EverySeconds&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="nf"&gt;PreventOverlapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProcessAllOrdersInvocable&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;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're scheduling one job, &lt;code&gt;ProcessAllOrdersInvocable&lt;/code&gt;, to run every 5 seconds. If another instance of this job is running at a given due time, we skip that run. This way it doesn't step all over its own feet 😅.&lt;/p&gt;

&lt;p&gt;Here's the Coravel invocable &lt;code&gt;ProcessAllOrdersInvocable&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;using&lt;/span&gt; &lt;span class="nn"&gt;System.Diagnostics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Coravel.Invocable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Dapper&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.SqlClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Basic&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;class&lt;/span&gt; &lt;span class="nc"&gt;ProcessAllOrdersInvocable&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IInvocable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;connectionString&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;ProcessAllOrdersInvocable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&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;connectionString&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;Invoke&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;lastIdProcessed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&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;watch&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;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connection&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;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&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="k"&gt;while&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="p"&gt;{&lt;/span&gt;          
            &lt;span class="kt"&gt;var&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryAsync&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="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;OrderDate&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;SQL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;LastIdProcessed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lastIdProcessed&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;AsList&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="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;break&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;tasks&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;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;Count&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;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;SimulateProcessOrderAsync&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="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;lastIdProcessed&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="nf"&gt;Last&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&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="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProcessAllOrdersInvocable&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt; took &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ms"&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;static&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;SimulateProcessOrderAsync&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;order&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;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SQL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"
SELECT TOP 100
    *
FROM Sales.Orders 
WHERE 
    OrderID &amp;gt; @LastIdProcessed
ORDER BY OrderID"&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;About this job:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loads all orders from the database into memory in chunks of 100&lt;/li&gt;
&lt;li&gt;For each item in a chunk/batch, it does some fake processing that takes 10 milliseconds&lt;/li&gt;
&lt;li&gt;The code stores each &lt;code&gt;Task&lt;/code&gt; from the processing method and awaits them all at the end for increased performance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Note: You might have noticed that the SQL query I'm executing is grabbing data from all the columns. This is to perform a more realistic query, even though our code doesn't use any column except &lt;code&gt;OrderId&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For this job to execute once, on my machine (Ryzen 7 4800), takes about 12 seconds when profiling.&lt;/p&gt;

&lt;p&gt;Profiling the memory usage we get about 32.9 MB of usage:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FN7hmAWo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FN7hmAWo.png" alt="memory usage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Increasing Batch Sizes
&lt;/h2&gt;

&lt;p&gt;12 seconds is too long! We need this to be within our SLA of 5 seconds to process all pending orders (we're a busy business).&lt;/p&gt;

&lt;p&gt;The next step we can take is to increase our batching size from 100 to let's say 5000 👀. This should reduce the amount of database calls we need to make.&lt;/p&gt;

&lt;p&gt;Doing this brings our processing time down to about 2 seconds.&lt;/p&gt;

&lt;p&gt;In the real world, if we are sending 5000 emails at the same time over the network a few "bad" things can occur:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We start getting an increase in overall latency in our network&lt;/li&gt;
&lt;li&gt;Our email provider starts rate-limiting our process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, our (fake) email provider is pretty super so we don't get any issues here.&lt;/p&gt;

&lt;p&gt;The results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Processing time is between 500 to 1000ms&lt;/li&gt;
&lt;li&gt;RAM usage is up to about 45MB&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding More CRON Jobs
&lt;/h2&gt;

&lt;p&gt;Over time more CRON jobs have been added to the application.&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;host&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;UseScheduler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduler&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;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessAllOrdersInvocable&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;EverySeconds&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="nf"&gt;PreventOverlapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProcessAllOrdersInvocable&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessAllCitiesInvocable&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;EverySeconds&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="nf"&gt;PreventOverlapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProcessAllCitiesInvocable&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessAllInvoicesInvocable&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;EverySeconds&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="nf"&gt;PreventOverlapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProcessAllInvoicesInvocable&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessAllStockItemTransactionsInvocable&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;EverySeconds&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="nf"&gt;PreventOverlapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProcessAllStockItemTransactionsInvocable&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;Some of these are hitting bigger database tables now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FAMg5PP8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FAMg5PP8.png" alt="bigger table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try to run all CRON jobs each with a batch of 5000 items assuming each record or item takes 10ms to "process" (whether that's sending emails or whatever).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F0jjHEbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F0jjHEbz.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FLbvZlS3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FLbvZlS3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not bad. But, I'm running on a powerful laptop. &lt;strong&gt;What if we ran this in a docker container with limited resources?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Real-World Scenario: Running In Limited Docker Containers
&lt;/h2&gt;

&lt;p&gt;I created a basic docker file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["Basic/Basic.csproj", "Basic/"]
RUN dotnet restore "Basic/Basic.csproj"
COPY . .
WORKDIR "/src/Basic"
RUN dotnet build "Basic.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Basic.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Basic.dll"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've configured my application to run with run options &lt;code&gt;--memory="200m" --cpus="1"&lt;/code&gt;. This is probably a more realistic scenario.&lt;/p&gt;

&lt;p&gt;To enforce this, I created a &lt;code&gt;docker-compose.yaml&lt;/code&gt; file to set resource limits:&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.3"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dotnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;..&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./Basic/Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;200M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: My connection string also needs to reference "host.docker.internal" instead of "localhost" now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So... what are the results?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fp6tnMLT.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fp6tnMLT.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Coravel's default configuration, jobs are executed one after the other. So, the total time it takes Coravel to process these 4 is the sum of all the logged values: &lt;em&gt;over 6 seconds.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FrjRbBxR.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FrjRbBxR.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This isn't meeting our SLA!&lt;/p&gt;

&lt;h2&gt;
  
  
  Coravel Schedule Workers
&lt;/h2&gt;

&lt;p&gt;The reason why Coravel runs CRON jobs one by one is so that it doesn't hog extra threads. In Web applications, this is a really good thing!&lt;/p&gt;

&lt;p&gt;Coravel has a feature to dedicate more threads in order to isolate and parallelize jobs. This is called &lt;a href="https://docs.coravel.net/Scheduler/#schedule-workers" rel="noopener noreferrer"&gt;Schedule Workers&lt;/a&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="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessAllOrdersInvocable&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;EverySeconds&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="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessAllCitiesInvocable&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;EverySeconds&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="c1"&gt;// Dedicating a separate thread for this job.&lt;/span&gt;
&lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProcessAllStockItemTransactionsInvocable&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessAllStockItemTransactionsInvocable&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;EverySeconds&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="c1"&gt;// Dedicating a separate thread for this job too.&lt;/span&gt;
&lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProcessAllInvoicesInvocable&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessAllInvoicesInvocable&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;EverySeconds&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typically, schedule workers are useful for isolating longer-running tasks so they don't hold up other shorter-running tasks. But, does this help if we give one of the jobs a dedicated thread when it runs?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FaZMwU9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FaZMwU9k.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keeping in mind that most of these jobs are running in parallel, the total time it took to run the 4 jobs is the job that took the longest: ~3.7 seconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fn8Eva5e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fn8Eva5e.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, we're seeing the time it takes individual jobs actually go up a bit. It might be that the overhead of having to manage multiple threads to do work is being throttled/limited by our container's measly 1 CPU allocation.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Power!
&lt;/h2&gt;

&lt;p&gt;Bumping the configuration to 2 CPUs gives us:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FBLeT14M.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FBLeT14M.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 3 first jobs are definitely running faster. However, that last job isn't. It might be limited by database resourcing or it still might not have enough CPU power.&lt;/p&gt;

&lt;p&gt;To verify, let's bump the CPU limit to 16 (my machine's total logical CPU count):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FvAv3QlN.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FvAv3QlN.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;No material change here.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If we were okay with the results we have, the next steps might be to play around with the code of the &lt;code&gt;ProcessAllInvoicesInvocable&lt;/code&gt;. We could try to change how large the batches of data it fetches from the database are, verify and optimize database indexing, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed Processing
&lt;/h2&gt;

&lt;p&gt;We need more processing power. But, our organization only allows us to configure up to 1 docker CPU per running container! (Pesky governance!)&lt;/p&gt;

&lt;p&gt;What if we could split the processing of scheduled jobs across multiple running processes though?&lt;/p&gt;

&lt;p&gt;There's a great library for using distributed locks called DistributedLock. Go figure.&lt;/p&gt;

&lt;p&gt;There's a specific package that supports &lt;a href="https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.SqlServer.md" rel="noopener noreferrer"&gt;locking with SQLServer&lt;/a&gt; that we'll start with.&lt;/p&gt;

&lt;p&gt;Our invocables now look like this:&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;Invoke&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;@lock&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;SqlDistributedLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProcessAllCitiesInvocable&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;this&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="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handle&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;@lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryAcquireAsync&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="n"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// do stuff&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;I also changed &lt;code&gt;docker-compose-yaml&lt;/code&gt; to run 3 instances of this project. Here are the results where you can see that jobs aren't overlapping and are handled by different instances:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F7Jc4M4Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F7Jc4M4Q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The benefit of this approach, much like using Coravel's schedule workers, is that if one job is taking too long to process it won't cause other jobs to wait for it. Another instance/process will pick up those other jobs.&lt;/p&gt;

&lt;p&gt;It also looks like we have a bottleneck with &lt;code&gt;ProcessAllInvoices&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This bottleneck on my machine could be due to disk speed, memory speed, CPU, or something else. &lt;em&gt;This might just be another reminder that the database is such an important piece of your performance profile!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Potential Issue
&lt;/h2&gt;

&lt;p&gt;There's one issue to consider though. If you absolutely need to ensure that a specific job only runs once every 5 seconds, &lt;strong&gt;this approach won't work.&lt;/strong&gt; Look at the screenshot above closely to see this - some jobs are executed multiple times within a span of 5 seconds even though different instances are running the job.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let's say your job X takes 500 ms to run. On Instance 1 it runs at 12:00:00 p.m. &lt;/li&gt;
&lt;li&gt;Instance 2 also sees that job X is due at the same time. So instance 2 starts to run a batch of all the jobs that are due. &lt;/li&gt;
&lt;li&gt;Instance 2 runs a job Y first, which takes about 600 ms.&lt;/li&gt;
&lt;li&gt;Then, at 12:00:60 p.m., it tries to get a lock on job X. &lt;/li&gt;
&lt;li&gt;It gets a lock since job X on instance 1 finished running 100 ms ago.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you don't need this kind of exact timing guarantee within a small time span of work, then this distributed lock approach might work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FBUwLDwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FBUwLDwi.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoupling Distributed CRON Jobs
&lt;/h2&gt;

&lt;p&gt;The way that we've architected our CRON jobs might work for most teams. But, one of the other downsides is that we've coupled our scheduling logic with our job logic. Sometimes that's okay. &lt;/p&gt;

&lt;p&gt;It also has the concurrency issue mentioned in the section above.&lt;/p&gt;

&lt;p&gt;If we wanted to scale this solution so that other teams in our organization could use it, it wouldn't work. &lt;em&gt;Other teams shouldn't be able to add their own invocable/job logic in our code. Our code would become a dumping ground!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate Scheduling Logic From Job Logic
&lt;/h2&gt;

&lt;p&gt;The next step is to separate the scheduling logic from the CRON job logic. If we were using Coravel, then we'd make Coravel push an asynchronous message to a message broker. &lt;/p&gt;

&lt;p&gt;We'd have to determine what the lowest interval or "tick" is. Every second? 5 seconds? Minute (like normal CRON in an OS)?&lt;/p&gt;

&lt;p&gt;Coravel would send a message every interval X with the exact time that it ran.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fz2whp57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fz2whp57.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every team/consumer would get a message each interval that they should execute their job logic based on what time it is. But each team now owns that code.&lt;/p&gt;

&lt;p&gt;Alternately, this approach could also help with the issue we saw where different instances executed the same job multiple times. Now, we're only triggering "ticks" once across all distributed systems and could leverage the messaging technology to only send each tick to one consumer in a round-robin fashion (or some other load-balancing technique).&lt;/p&gt;

&lt;h2&gt;
  
  
  An Alternate Way To Measure Performance
&lt;/h2&gt;

&lt;p&gt;Let's go back to the beginning. Our assumption was that our entire batch of jobs had to run within 5 seconds.&lt;/p&gt;

&lt;p&gt;One other way to measure the performance of the 3 different approaches we looked at (serial, multi-threads &amp;amp; multi-instance) is to calculate performance based on "database records processed per second".&lt;/p&gt;

&lt;p&gt;What if our SLA wasn't &lt;em&gt;"process 1 batch in 5 seconds" but "process X number of records every X seconds"?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, I made the changes needed. For example, here's the extra job to output the value:&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;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Schedule&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="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"### Total records processed: "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;TotalRecordsProcessed&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;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EverySecond&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each job adds the number of saved records to the database using &lt;code&gt;Interlocked&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I ran these using the same &lt;code&gt;docker-compose&lt;/code&gt; files created across each project with a CPU limit of "2" for each instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Scheduler Results
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### Total records processed: 10000
### Total records processed: 30000
### Total records processed: 50000
### Total records processed: 85510
### Total records processed: 415772
### Total records processed: 438712
### Total records processed: 453712
### Total records processed: 478712
### Total records processed: 574222
### Total records processed: 847424
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have an average of 84,742 records per second (847,424 records / 10 runs).&lt;/p&gt;

&lt;h3&gt;
  
  
  Schedule Workers Results
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### Total records processed: 80000
### Total records processed: 273595
### Total records processed: 281535
### Total records processed: 393202
### Total records processed: 413202
### Total records processed: 637307
### Total records processed: 685247
### Total records processed: 801914
### Total records processed: 821914
### Total records processed: 837424
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have an average of 83,742 records per second (837,424 records / 10 runs).&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed Instances
&lt;/h2&gt;

&lt;p&gt;For these results, we have to take 10 seconds of activity across 3 nodes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;distributed-dotnet3-1  | ### Total records processed: 15000
distributed-dotnet1-1  | ### Total records processed: 20000
distributed-dotnet2-1  | ### Total records processed: 15000
distributed-dotnet3-1  | ### Total records processed: 92940
distributed-dotnet1-1  | ### Total records processed: 93595
distributed-dotnet1-1  | ### Total records processed: 111535
distributed-dotnet2-1  | ### Total records processed: 30000
distributed-dotnet1-1  | ### Total records processed: 111535
distributed-dotnet3-1  | ### Total records processed: 227940
distributed-dotnet2-1  | ### Total records processed: 50000
distributed-dotnet3-1  | ### Total records processed: 334607
distributed-dotnet1-1  | ### Total records processed: 111535
distributed-dotnet3-1  | ### Total records processed: 348202
distributed-dotnet2-1  | ### Total records processed: 95510
distributed-dotnet2-1  | ### Total records processed: 108450
distributed-dotnet2-1  | ### Total records processed: 108450
distributed-dotnet3-1  | ### Total records processed: 448202
distributed-dotnet1-1  | ### Total records processed: 195130
distributed-dotnet1-1  | ### Total records processed: 223070
distributed-dotnet2-1  | ### Total records processed: 108450
distributed-dotnet1-1  | ### Total records processed: 228070
distributed-dotnet3-1  | ### Total records processed: 548202
distributed-dotnet2-1  | ### Total records processed: 108450
distributed-dotnet1-1  | ### Total records processed: 243070
distributed-dotnet3-1  | ### Total records processed: 654869
distributed-dotnet3-1  | ### Total records processed: 658464
distributed-dotnet2-1  | ### Total records processed: 108450
distributed-dotnet3-1  | ### Total records processed: 658464
distributed-dotnet1-1  | ### Total records processed: 263070
distributed-dotnet2-1  | ### Total records processed: 146390
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have an average of 106,810 records per second.&lt;/p&gt;

&lt;p&gt;Cool! But is that because we are actually running more tasks than we should? (Remember the locking issue? The answer turns out to be "yes" 😅)&lt;/p&gt;

&lt;p&gt;I tried again by changing the code so that each instance only runs explicitly 1 or 2 jobs (e.g. no sharing across instances):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;distributed-dotnet1-1  | ### Total records processed: 0
distributed-dotnet2-1  | ### Total records processed: 45000
distributed-dotnet3-1  | ### Total records processed: 5000
distributed-dotnet1-1  | ### Total records processed: 20000
distributed-dotnet2-1  | ### Total records processed: 155000
distributed-dotnet3-1  | ### Total records processed: 15000
distributed-dotnet1-1  | ### Total records processed: 87940
distributed-dotnet2-1  | ### Total records processed: 236667
distributed-dotnet3-1  | ### Total records processed: 30000
distributed-dotnet1-1  | ### Total records processed: 111535
distributed-dotnet2-1  | ### Total records processed: 236667
distributed-dotnet3-1  | ### Total records processed: 45000
distributed-dotnet1-1  | ### Total records processed: 111535
distributed-dotnet2-1  | ### Total records processed: 236667
distributed-dotnet3-1  | ### Total records processed: 60000
distributed-dotnet1-1  | ### Total records processed: 111535
distributed-dotnet2-1  | ### Total records processed: 351667
distributed-dotnet3-1  | ### Total records processed: 70510
distributed-dotnet1-1  | ### Total records processed: 214475
distributed-dotnet2-1  | ### Total records processed: 473334
distributed-dotnet3-1  | ### Total records processed: 70510
distributed-dotnet1-1  | ### Total records processed: 223070
distributed-dotnet2-1  | ### Total records processed: 473334
distributed-dotnet3-1  | ### Total records processed: 70510
distributed-dotnet1-1  | ### Total records processed: 223070
distributed-dotnet2-1  | ### Total records processed: 473334
distributed-dotnet3-1  | ### Total records processed: 70510
distributed-dotnet1-1  | ### Total records processed: 223070
distributed-dotnet2-1  | ### Total records processed: 473334
distributed-dotnet3-1  | ### Total records processed: 70510
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's roughly about 76,690 records per second.&lt;/p&gt;

&lt;p&gt;While not super scientific, using 1 .NET instance with &lt;a href="https://github.com/jamesmh/coravel" rel="noopener noreferrer"&gt;Coravel&lt;/a&gt; was able to perform just as well as using multiple instances.&lt;/p&gt;

&lt;p&gt;Keep in mind that we are measuring overall throughput across our entire processing with this measurement vs. the performance of an entire batch that we expect to run within 5 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The conclusions of our not-super-scientific-but-fun experiment are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changing the size of batches that you fetch from the database can dramatically improve the performance of an individual CRON job 💰&lt;/li&gt;
&lt;li&gt;Even though we didn't 100% verify (in my experience this is often true): database performance is often a bottleneck 🍾&lt;/li&gt;
&lt;li&gt;1 process using &lt;a href="https://github.com/jamesmh/coravel" rel="noopener noreferrer"&gt;Coravel&lt;/a&gt; to run your CRON jobs is pretty efficient compared to running multiple processes 🚀&lt;/li&gt;
&lt;li&gt;Only distribute work if you know it's going to help as it can introduce unseen issues. Make sure you understand what's going on. &lt;strong&gt;Distributed systems are hard&lt;/strong&gt; 🙄&lt;/li&gt;
&lt;li&gt;For high-performance CRON processing across teams, you should decouple scheduling logic from job logic 💻&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out &lt;a href="https://github.com/jamesmh/high-performance-dotnet-cron-jobs" rel="noopener noreferrer"&gt;the repository with code&lt;/a&gt; that you can play around with. Give &lt;a href="https://github.com/jamesmh/coravel" rel="noopener noreferrer"&gt;Coravel&lt;/a&gt; a try in your own projects if you haven't yet!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>What Are Aggregates In Domain-Driven Design?</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Wed, 03 May 2023 03:12:20 +0000</pubDate>
      <link>https://forem.com/jamesmh/what-are-aggregates-in-domain-driven-design-16nh</link>
      <guid>https://forem.com/jamesmh/what-are-aggregates-in-domain-driven-design-16nh</guid>
      <description>&lt;p&gt;Aggregates are one of the most misunderstood concepts in &lt;a href="https://www.jamesmichaelhickey.com/focus-core-domain/" rel="noopener noreferrer"&gt;domain-driven design.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What is an aggregate? Sure, it's a pattern that's central to domain-driven design... but is it just a collection of objects?&lt;/p&gt;

&lt;p&gt;Martin Fowler explains:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Aggregates are the basic element of transfer of data storage - you request to load or save whole aggregates. Transactions should not cross aggregate boundaries.&lt;br&gt;
&lt;em&gt;&lt;a href="https://www.martinfowler.com/bliki/DDD_Aggregate.html" rel="noopener noreferrer"&gt;https://www.martinfowler.com/bliki/DDD_Aggregate.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those with experience in DDD might understand what that means and why it applies.&lt;/p&gt;

&lt;p&gt;But for those starting to get familiar with aggregates, such an explanation might still be too detailed and nuanced.&lt;/p&gt;

&lt;p&gt;Let's start looking at what an aggregate &lt;em&gt;is not.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What An Aggregate Is Not
&lt;/h2&gt;

&lt;p&gt;An aggregate is not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just a graph of entities&lt;/li&gt;
&lt;li&gt;Merely a behaviour rich object&lt;/li&gt;
&lt;li&gt;An entity or collection of entities that you can dump into your database tables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So... what is it?&lt;/p&gt;

&lt;p&gt;This is usually where people start talking about consistency boundaries, transactional consistency, eventual consistency, &lt;a href="https://www.jamesmichaelhickey.com/consistency-boundary/" rel="noopener noreferrer"&gt;aggregate boundaries&lt;/a&gt;, invariants, aggregate roots, etc.&lt;/p&gt;

&lt;p&gt;When learning about these things, it's natural to grab onto a familiar term or idea when all this jargon is thrown at us. From there, we (falsely) form an idea of what this is all about.&lt;/p&gt;

&lt;p&gt;Let's try to keep things simple and practical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bubbles
&lt;/h2&gt;

&lt;p&gt;I like to use a very simple idea to help people understand the essence of what aggregates are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Bubbles.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine your software project was not one massive codebase - but a collection of small bubbles. Each bubble can be worked on independently. That means, you only need to think about what's in the bubble at any given moment - not the entire system all-at-once.&lt;/p&gt;

&lt;p&gt;Aggregates are the same. They are bubbles. Just on a smaller scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case: Teams
&lt;/h2&gt;

&lt;p&gt;Imagine we are building a new feature for our system. This new feature includes the concept of projects, teams and team members.&lt;/p&gt;

&lt;p&gt;Each team can have multiple staff members associated with it. &lt;/p&gt;

&lt;p&gt;Staff members can be a part of multiple teams.&lt;/p&gt;

&lt;p&gt;Each team can have multiple projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhuc1d9w1ti1jgjnqjljg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhuc1d9w1ti1jgjnqjljg.png" alt="Team and project relationships"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, that's simple enough. &lt;/p&gt;

&lt;p&gt;Let's add the fields that might exist on each object:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gy8f4i76j4nk0mmue2y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gy8f4i76j4nk0mmue2y.png" alt="Team and project fields."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Database All The Things
&lt;/h2&gt;

&lt;p&gt;Doesn't that look just like an &lt;a href="https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model" rel="noopener noreferrer"&gt;entity diagram&lt;/a&gt;? Don't the database tables scream out at you?&lt;/p&gt;

&lt;p&gt;"Obviously", we need to have a composite table linking each team with each team member.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Just hold on.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's think about behaviour instead and not treat these as code objects. Let's treat them as business objects or concepts. What can these objects do?&lt;/p&gt;

&lt;p&gt;Well, isn't it clear? You can &lt;em&gt;create&lt;/em&gt; a team. &lt;em&gt;Edit&lt;/em&gt; a team. &lt;em&gt;Delete&lt;/em&gt; a team.&lt;/p&gt;

&lt;p&gt;Obviously, deleting a team means that all the associated projects ought to cascade and be deleted too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9rznh9hzi5c7suii3i2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9rznh9hzi5c7suii3i2.png" alt="Cascading deletes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Let's put aside the fact that these are not the real behaviours of our system. Anytime you see CRUDy language, it should be a red flag!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Business Is Not So Simple
&lt;/h2&gt;

&lt;p&gt;But wait. You just found out from your users that this won't work. The assumptions you made about the business were wrong...&lt;/p&gt;

&lt;p&gt;There &lt;strong&gt;&lt;em&gt;are&lt;/em&gt;&lt;/strong&gt; times when projects are moved from one team to another. &lt;/p&gt;

&lt;p&gt;There &lt;strong&gt;&lt;em&gt;are&lt;/em&gt;&lt;/strong&gt; times when projects are orphaned for a period of time.&lt;/p&gt;

&lt;p&gt;So now, other questions arise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What should our model look like now?&lt;/li&gt;
&lt;li&gt;When we write our code, should we load the entire graph of objects into memory?&lt;/li&gt;
&lt;li&gt;What happens when a project is orphaned? Will the teams just have a reference to a null project object?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufe3yirwqr5cnkh4gvt0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufe3yirwqr5cnkh4gvt0.png" alt="More questions to ask about our model."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More Requirements That Complicate Things
&lt;/h2&gt;

&lt;p&gt;Now the business has a new requirement: &lt;strong&gt;a team member's role can change per project.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax2bvh3fh4rc28kalsa7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax2bvh3fh4rc28kalsa7.png" alt="Team member role can be exist per project."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So... do we just create another composite table to match each team member with each project they are on and the role of each project?&lt;/p&gt;

&lt;p&gt;That's what we usually do. &lt;strong&gt;Developers naturally think about systems in terms of database design first&lt;/strong&gt; 🤦‍♂️.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Yes, that's a huge problem that domain-driven design tries to help avoid!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With each new requirement, our model gets more bloated.&lt;/strong&gt; Over time, this might consume lots of memory in our system too.&lt;/p&gt;

&lt;p&gt;Imagine a project whose team has 500 members. Yes, these are large projects we're talking about.&lt;/p&gt;

&lt;p&gt;We need to load all the staff members into memory, and all their data too!&lt;/p&gt;

&lt;p&gt;That will lead to performance issues around memory usage, etc. Is there a better way?&lt;/p&gt;

&lt;h2&gt;
  
  
  Aggregates
&lt;/h2&gt;

&lt;p&gt;Aggregates are what solve these kind of problems.&lt;/p&gt;

&lt;p&gt;They help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplify our models when they start getting out of hand&lt;/li&gt;
&lt;li&gt;Isolate complex business rules&lt;/li&gt;
&lt;li&gt;Deal with performance issues when loading large object graphs into memory&lt;/li&gt;
&lt;li&gt;Allow flexibility to more easily deal with future unexpected business requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's what aggregates &lt;em&gt;are for.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ok. But &lt;em&gt;what are they?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Instead of telling you, I'll show you what one might look like in this case (otherwise, we need to start talking about consistency, &lt;a href="https://www.jamesmichaelhickey.com/consistency-boundary/" rel="noopener noreferrer"&gt;transactional boundaries&lt;/a&gt;, concurrency, etc!).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1w3dj1pdecm5ixw2m6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1w3dj1pdecm5ixw2m6i.png" alt="Aggregate diagram."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that I split the original Member model into three?&lt;/p&gt;

&lt;p&gt;The Team and the Project have their own dedicated "version" or "view" of the Member that only has the exact data it needs to make decisions about business rules and behaviours within it's "bubble".&lt;/p&gt;

&lt;p&gt;For example, the Member's role is not needed by the team bubble. Why keep it there when it doesn't belong?&lt;/p&gt;

&lt;p&gt;Instead, we have split our model into two "branches". Two bubbles/aggregates, in this case. &lt;/p&gt;

&lt;p&gt;To support being able to assign the same Member to multiple teams, we then have to create a dedicated authoritative model of a Team Member and link the other aggregate's Member entity as a foreign key-like reference &lt;em&gt;(again, we aren't talking about databases).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6zltcx69ro16u4ncpi0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6zltcx69ro16u4ncpi0.png" alt="Domain-driven design model diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Notice I added specific Ids to the non-authoritative "Member" models (like "TeamMemberId")&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There's much more to discuss. And many more improvements we could make to this model around using value objects, etc.&lt;/p&gt;

&lt;p&gt;I think this is enough in order to help you see that aggregates are more than simply creating a graph of entities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's all about allowing the domain rules to guide you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many times, the aggregates we discover are not the aggregates we thought we would need!&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>ddd</category>
      <category>programming</category>
    </item>
    <item>
      <title>Securing Your API With Long-Lived Authentication Keys (With A Deep-Dive!)</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Thu, 24 Feb 2022 19:34:29 +0000</pubDate>
      <link>https://forem.com/procedureflow/securing-your-api-with-long-lived-authentication-keys-496k</link>
      <guid>https://forem.com/procedureflow/securing-your-api-with-long-lived-authentication-keys-496k</guid>
      <description>&lt;p&gt;There comes a time in the life of a multi-tenant SAAS company when its larger customers need the automation and power of an API. The multitude of reasons includes scaling user provisioning, integrating with other products, facilitating advanced reporting and more.&lt;/p&gt;

&lt;p&gt;If you're in this position, one of the most important areas you need to think about carefully is authentication. There are many different factors to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is your API responding to server-to-server requests? Or will you need to support other types of clients like web applications, mobile, etc.?&lt;/li&gt;
&lt;li&gt;Do you need long-lived or short-lived tokens?&lt;/li&gt;
&lt;li&gt;What about OAuth? Does that make sense right now?&lt;/li&gt;
&lt;li&gt;Should you jump on the JWT bandwagon?&lt;/li&gt;
&lt;li&gt;How will your customers configure authentication? How does your choice on this affect their user and developer experience?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At ProcedureFlow, we've spent a fair amount of time researching how the best APIs out there have implemented API authentication: &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;, &lt;a href="https://slack.com/" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;, &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://www.twilio.com/" rel="noopener noreferrer"&gt;Twilio&lt;/a&gt; and others. We want to make sure our API is really easy to use, gives superpowers to our customers and is rock-solid secure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsatsyxv91za4mo6y9pwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsatsyxv91za4mo6y9pwe.png" alt="Rock solid API" width="800" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Needless to say, this one area can get overwhelming quickly! To figure out what works best for your company's needs, you have to start by thinking about the requirements of your API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimum Viable API
&lt;/h2&gt;

&lt;p&gt;If you're a startup like us, then you won't have the time and resources to build a complete API that will integrate with "all the things" immediately. You have to identify what API products make sense as a priority. Then, you can build your foundational API infrastructure, that first API product and iterate based on your customer's feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuz4xqey29e47kfcawrj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuz4xqey29e47kfcawrj.png" alt="Use An Iterative Approach To API Product Development" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, we decided to build a &lt;a href="http://www.simplecloud.info/" rel="noopener noreferrer"&gt;SCIM API&lt;/a&gt; first.&lt;/p&gt;

&lt;p&gt;Authentication is one of the areas where SCIM is flexible - so we can use the mechanism that works best for our custom API and use it for SCIM too 👍.&lt;/p&gt;

&lt;p&gt;Based on our requirements, there were a few smaller decisions we had to make first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We'd be focusing on server-to-server APIs for the first few API products so this influences what kind of authentication mechanism makes sense for us.&lt;/li&gt;
&lt;li&gt;We wanted to retain the ability to implement other authentication mechanisms in the future.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.postman.com/the-most-important-api-metric-is-time-to-first-call/" rel="noopener noreferrer"&gt;TTFC (Time-to-first-call)&lt;/a&gt; is considered one of the most important API metrics. Configuring authentication should be as simple as possible for our customers to facilitate a low TTFC.&lt;/li&gt;
&lt;li&gt;How do well-designed APIs like Stripe, Twilio, Slack, Twitter and GitHub do things? What can we learn or implement ourselves?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Big Decision
&lt;/h2&gt;

&lt;p&gt;Your overall decision will be based on a balance between valuing ease of use for your customers and enforcing solid security.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0s0s0dg2zlnb3mt1k68l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0s0s0dg2zlnb3mt1k68l.png" alt="ease-of-use vs. security" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At ProcedureFlow, we decided to use a long-lived API key that our customers can generate from within our product. This is similar to how &lt;a href="https://stripe.com/docs/keys" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; and &lt;a href="https://developer.twitter.com/en/docs/tutorials/step-by-step-guide-to-making-your-first-request-to-the-twitter-api-v2" rel="noopener noreferrer"&gt;Twitter V2&lt;/a&gt; do API authentication. We generate both a &lt;code&gt;client_id&lt;/code&gt; and a &lt;code&gt;client_secret&lt;/code&gt; (more on these later 😉).&lt;/p&gt;

&lt;p&gt;Some of the qualities and behaviors of using a long-lived API key includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys can be tied to individual tenants or specific users &lt;a href="https://api.slack.com/authentication/token-types" rel="noopener noreferrer"&gt;like Slack does&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Can be scoped to specific products or features &lt;a href="https://stripe.com/docs/keys#limit-access" rel="noopener noreferrer"&gt;like Stripe&lt;/a&gt; does, with read-only or read/write access per feature.&lt;/li&gt;
&lt;li&gt;Also inspired by &lt;a href="https://stripe.com/docs/keys#rolling-keys" rel="noopener noreferrer"&gt;Stripe's approach&lt;/a&gt;, customers can have a user-friendly UI to generate new keys, revoke them immediately or revoke them with a grace period.&lt;/li&gt;
&lt;li&gt;API secrets should be hidden on the UI, except when first generated. This is mainly inspired by &lt;a href="https://stripe.com/docs/keys#safe-keys" rel="noopener noreferrer"&gt;Stripe's approach&lt;/a&gt; and is a best practice. You don't want those secrets getting leaked!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another thing we chose to do is prefix our key values. This makes them easier to identify and makes the type of key/token more explicit. This technique is inspired by Stripe and GitHub - &lt;a href="https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/" rel="noopener noreferrer"&gt;who have recently switched to this format too&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep-Dive: Long-lived API Token Authentication
&lt;/h2&gt;

&lt;p&gt;Let's deep-dive into some of the more detailed decisions we had to make and why they might make sense for you too.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Anatomy Of An API Key
&lt;/h3&gt;

&lt;p&gt;Services like Stripe, Slack and Twilio use two values for their long-lived API keys. Slack, for example, calls these &lt;a href="https://api.slack.com/authentication/best-practices#slack_apps__client-id-and-secret" rel="noopener noreferrer"&gt;the Client Id and Client Secret&lt;/a&gt;. Stripe calls them &lt;a href="https://stripe.com/docs/keys#obtain-api-keys" rel="noopener noreferrer"&gt;the Publishable and Secret Keys&lt;/a&gt;. Other vendors have used other terms like "Access Key" and "Access Key Secret".&lt;/p&gt;

&lt;p&gt;We have chosen to call ours &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;. This helps to set us up for future use-cases around delegated authorization (e.g. OAuth) and is consistent with the standard OAuth naming terminology. Also, each token can be used by a specific client - web, mobile, server, etc. so &lt;code&gt;client_id&lt;/code&gt; seems more semantically accurate to us than other options like &lt;code&gt;app_id&lt;/code&gt;, &lt;code&gt;access_key&lt;/code&gt; or &lt;code&gt;public_key&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Public Identifier
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;client_id&lt;/code&gt; is used to identify the user or owner of the API key.&lt;/p&gt;

&lt;p&gt;This is helpful when you may have multiple types of clients that use your API keys - web applications, mobile applications, back-end systems, etc. It also helps when tokens can be assigned to specific users in your system. You can use the &lt;code&gt;client_id&lt;/code&gt; to peg rate limits per client, logging metrics that are tied to the identifier, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secret Token
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;client_secret&lt;/code&gt; is a random cryptographically generated token.&lt;/p&gt;

&lt;p&gt;When thinking about the technical aspects of implementing API key authentication, one of the most important issues to make sure you consider is that &lt;em&gt;API secrets are &lt;a href="https://aws.amazon.com/blogs/security/wheres-my-secret-access-key/" rel="noopener noreferrer"&gt;just like passwords&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is inappropriate to store these tokens as plain-text for the same reasons you wouldn't store passwords as plain-text. Even worse, API secrets are present for every HTTP request, so you want to make sure to protect them well.&lt;/p&gt;

&lt;p&gt;Like passwords, API secrets ought to be hashed when stored in a database or some other tool like a cache. If an attacker ever managed to get access to your database, then they could start impersonating your customers by using plain-text API secrets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5z0oyqpgux4s2pu91lx9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5z0oyqpgux4s2pu91lx9.png" alt="identity vs. authorization" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Security Considerations
&lt;/h3&gt;

&lt;p&gt;One of the reasons you may not want to only use a "secret" value, even if hashed, is that it becomes both the secret and the lookup value.&lt;/p&gt;

&lt;p&gt;When an API request comes into your system, and if all you have is the secret value, then you would have to hash the secret and check if it exists in your database. If it does exist, then you know who owns the API key and therefore who's allowed to perform the API request.&lt;/p&gt;

&lt;p&gt;Your SQL query would look similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select * from api_auth_keys where secret_hash = $secret_hash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One potential problem with this approach is that it &lt;a href="https://blog.ircmaxell.com/2014/11/its-all-about-time.html" rel="noopener noreferrer"&gt;could be susceptible to timing attacks&lt;/a&gt;. While in practice there are multiple ways to mitigate these kinds of security vulnerabilities, the issue simply disappears if you use a different value as the identifier for the lookup - assuming you compare the hashes in memory by using a safe constant time comparison function.&lt;/p&gt;
&lt;h3&gt;
  
  
  Bearer Token
&lt;/h3&gt;

&lt;p&gt;At the moment, we are only using API keys for server-to-server communication. This means our customer's servers will need to communicate directly with our API as opposed to via some delegated third-party system (which would require something like OAuth).&lt;/p&gt;

&lt;p&gt;Because of this, we can combine both the &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; and display that to our users as one value. This helps with ease of use and user experience, yet allows us to still gain the benefits of using a &lt;a href="https://paragonie.com/blog/2017/02/split-tokens-token-based-authentication-protocols-without-side-channels" rel="noopener noreferrer"&gt;split token approach&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Facttyqv0nzqvgj6njt06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Facttyqv0nzqvgj6njt06.png" alt="Bearer Token Using A Split Token" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Best Hashing Algorithm?
&lt;/h3&gt;

&lt;p&gt;PBKDF2 is one of the most popular and reliable hashing algorithms today. It's a great algorithm for password hashing because it's slow and cryptographically sound. You don't want attackers to brute-force guess passwords quickly, so PBKFD2 makes that process much slower and therefore harder to attack.&lt;/p&gt;

&lt;p&gt;That's great - but one of the hallmarks of any high-quality API is having a short response time. This affects developer experience, can cause more load on servers (which means paying money for more servers), can affect contractual issues due to not meeting certain SLAs and can affect the overall perception of your products and company.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Is PBKFD2 the best option then?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;SHA256 is another common hashing algorithm that is solid - but it's quick to compute hashes. But, is there a way to use SHA256 so that hashes are still computed quickly but have high entropy so that attackers can't brute force API tokens within a reasonable amount of time?&lt;/p&gt;

&lt;p&gt;Two core problems with user-supplied passwords are that they are generally &lt;em&gt;short and predictable&lt;/em&gt;. So, what you need are API tokens that are long and random!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wg9eq7kt6t4bz7tiibb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wg9eq7kt6t4bz7tiibb.png" alt="SHA256 Hash" width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what we did - we opted to generate very long random values for our &lt;code&gt;client_secret&lt;/code&gt; - and we hash this using SHA256.&lt;br&gt;
The code for this might look like the following:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  Storing API Tokens
&lt;/h3&gt;

&lt;p&gt;You already know that you should store the hash of an API secret. However, you might also want to store the last few digits of your API secret.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stripe.com/docs/keys#safe-keys" rel="noopener noreferrer"&gt;Like Stripe&lt;/a&gt;, you should only show the raw API secret immediately after it is created. From that point forward, show the last few digits of the token on the UI. This helps customers identify which key they want to revoke, change scopes for, etc.&lt;/p&gt;

&lt;p&gt;We opted to follow suit with Stripe and keep 4 digits from our non-hashed API secrets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzkv5r735s5c98ydr2i0a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzkv5r735s5c98ydr2i0a.png" alt="Mock API Key Schema" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;client_id&lt;/code&gt; column would also benefit from using an index so that your database lookups performed during incoming API request authentication are fast 🚀.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Token Pipeline
&lt;/h3&gt;

&lt;p&gt;Whatever technology stack you are using, you'll want to verify any incoming API tokens early in the lifetime of an HTTP request.&lt;/p&gt;

&lt;p&gt;While .NET can sometimes get a bad rap, we use it for reasons like being one of the &lt;a href="https://octoverse.github.com/static/github-octoverse-2020-security-report.pdf" rel="noopener noreferrer"&gt;most secure web development platforms out there&lt;/a&gt;, having most of the basic tools you'll need for web development out of the box and having a robust type system. We might write more on that topic another time 😅.&lt;/p&gt;

&lt;p&gt;.NET's MVC framework has a flexible middleware system that's easy to hook into. We use the built-in middleware (a.k.a. "filters") to process API tokens early in the HTTP pipeline.&lt;/p&gt;

&lt;p&gt;Here's the flow of how we process bearer tokens when authenticating against our API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A client issues an HTTP request with an API token as a bearer token.&lt;/li&gt;
&lt;li&gt;We intercept the bearer token early in the HTTP pipeline.&lt;/li&gt;
&lt;li&gt;We split the token into &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;client_id&lt;/code&gt; we check if a row exists in the database.&lt;/li&gt;
&lt;li&gt;We hash the user input &lt;code&gt;client_secret&lt;/code&gt; and use a constant-time comparison function to verify that it matches the stored hash.&lt;/li&gt;
&lt;li&gt;We create a "session" object that only exists in memory for the lifetime of the HTTP request.&lt;/li&gt;
&lt;li&gt;Other events occur downstream that use the API key - like rate limiting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Formis7po5bqwj5u2l37v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Formis7po5bqwj5u2l37v.png" alt="API authentication process" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core of our API authentication middleware looks roughly like the following:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There's much more you'll have to consider when implementing API authentication and API tokens in particular:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resource/feature scopes&lt;/li&gt;
&lt;li&gt;UI elements&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Tracking token usage for troubleshooting&lt;/li&gt;
&lt;li&gt;Database indexing&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, we had to extend our existing Redis-based rate-limiting to support limiting based on API keys. Originally, limit data was not being made available to the caller. We had to modify our LUA scripts to return rate limit data and then assign values to the appropriate HTTP headers.&lt;/p&gt;

&lt;p&gt;We do our best at working fast and smart so that we can deliver value to our customers quickly. But we also care about the details, especially when decisions we make now can have an impact on the future of our products. For example, choosing to prefix our API tokens may seem insignificant at face value, but it affects user experience, the overall security of our API, and is not trivial to change in the future.&lt;/p&gt;

&lt;p&gt;If you like solving problems like the ones we've discussed and you're interested in the possibility of joining our team then check out our &lt;a href="https://procedureflow.com/jobs" rel="noopener noreferrer"&gt;open positions&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>api</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Building a Scalable E-Commerce Data Model</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Tue, 29 Dec 2020 21:31:07 +0000</pubDate>
      <link>https://forem.com/fabric_commerce/building-a-scalable-e-commerce-data-model-p9l</link>
      <guid>https://forem.com/fabric_commerce/building-a-scalable-e-commerce-data-model-p9l</guid>
      <description>&lt;p&gt;If selling products online is a core part of your business, then you need to build an e-commerce data model that’s scalable, flexible, and fast. Most off-the-shelf providers like Shopify and BigCommerce are built for small stores selling a few million dollars in orders per month, so many e-commerce retailers working at scale start to investigate creating a bespoke solution.&lt;/p&gt;

&lt;p&gt;This article will look at what it takes to start building this infrastructure on your own. What are some of the areas to consider? What might the data model look like? How much work is involved?&lt;/p&gt;

&lt;p&gt;Along the way, we’ll explore an alternative: API-based commerce platforms that manage data for you across product catalogs, pricing, and orders—without locking you into a monolith, and without requiring you to replatform.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;: A full summary diagram of the e-commerce data model is at the end of the article.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Are Your Customers?
&lt;/h2&gt;

&lt;p&gt;First, you need to consider who will be purchasing items from your e-commerce application. How might you model customer information in a database as a result? You’ll probably want to have basic information like your customer's name, email address, etc. Do you want your customers to be able to create a profile in your system? Or just fill out a form each time they want to purchase something?&lt;/p&gt;

&lt;p&gt;Just starting out, a basic model might look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FBasic%2520e-commerce%2520customer%2520data%2520model.png%3Fwidth%3D462%26name%3DBasic%2520e-commerce%2520customer%2520data%2520model.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FBasic%2520e-commerce%2520customer%2520data%2520model.png%3Fwidth%3D462%26name%3DBasic%2520e-commerce%2520customer%2520data%2520model.png" alt="Basic e-commerce customer data model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want your customers to have a persistent profile, then you need to build some way for them to log in to your application. Moving forward with more real-world requirements, you might also want to keep track of their login attempt history and password history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FMore%2520complex%2520e-commerce%2520customer%2520data%2520model.png%3Fwidth%3D1337%26name%3DMore%2520complex%2520e-commerce%2520customer%2520data%2520model.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FMore%2520complex%2520e-commerce%2520customer%2520data%2520model.png%3Fwidth%3D1337%26name%3DMore%2520complex%2520e-commerce%2520customer%2520data%2520model.png" alt="More complex e-commerce customer data model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might also want to consider whether your customers are part of a large organization; and, if so, how would they like to handle password resets? Do they need single sign-on or OAuth support?&lt;/p&gt;

&lt;h3&gt;
  
  
  Deep Dive: Addresses
&lt;/h3&gt;

&lt;p&gt;Did you notice there’s no address tied to a customer in any of the data models shown so far? It might be your first inclination to include a customer’s address as part of those models. However, most customers will have multiple addresses and multiple kinds of addresses, like billing and shipping. B2B retailers might also have to consider multiple delivery locations based on the number of warehouses and offices they support.&lt;/p&gt;

&lt;p&gt;What happens if the billing and shipping address are different? Well, you’ll need to do more than just add extra columns to the &lt;code&gt;Customer&lt;/code&gt; table! It’s not that simple.&lt;/p&gt;

&lt;p&gt;So how &lt;em&gt;does&lt;/em&gt; storing a billing address affect the scalability of your application?&lt;/p&gt;

&lt;p&gt;If you were to split the payment and shipping areas into separate (micro)services each having their own database, then putting billing and payment addresses into the &lt;code&gt;Customer&lt;/code&gt; area would lead to having “chatty” services. This is a &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/microservices-on-aws/chattiness.html" rel="noopener noreferrer"&gt;well-known design smell when building microservices.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To avoid this issue, you’re better off putting the addresses within the appropriate area/service that requires them, but with that, your data model becomes more complex.&lt;/p&gt;

&lt;p&gt;One way to avoid much of this complexity is to consider an &lt;a href="https://resources.fabric.inc/glossary/oms-software" rel="noopener noreferrer"&gt;order management system (OMS)&lt;/a&gt; by an API-first software provider. With this software, you can integrate the OMS into your data model without spending months of engineering time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Organize Products And Catalog?
&lt;/h2&gt;

&lt;p&gt;The first thing you see when you enter a store (either in-person or digitally) are products ready for you to purchase, and usually displayed with some thought for how you might be likely to shop.&lt;/p&gt;

&lt;p&gt;For an e-commerce web application, you will probably want to highlight things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Best selling products&lt;/li&gt;
&lt;li&gt;Trending products&lt;/li&gt;
&lt;li&gt;New products&lt;/li&gt;
&lt;li&gt;The ability to browse products by search criteria&lt;/li&gt;
&lt;li&gt;Providing customers with that information means you first need to keep track of a lot of data about your products: their prices, historical purchase data, and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s see what a “first shot” at creating a data model for a product catalog might look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FSimple%2520e-commerce%2520product%2520data%2520model.png%3Fwidth%3D843%26name%3DSimple%2520e-commerce%2520product%2520data%2520model.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FSimple%2520e-commerce%2520product%2520data%2520model.png%3Fwidth%3D843%26name%3DSimple%2520e-commerce%2520product%2520data%2520model.png" alt="Simple e-commerce product data model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s a &lt;code&gt;Product&lt;/code&gt; table with some basic information, like a product’s name, SKU, and price. The product is also linked to another table representing various categories that product is associated with. You might also strategically add indexes and full-text search to the &lt;code&gt;Product&lt;/code&gt; table to enable site visitors to efficiently search for various products.&lt;/p&gt;

&lt;p&gt;This is a decent first attempt. However, to get an even more realistic and useful e-commerce product catalog, you’ll need to support more requirements such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tracking pricing history so site administrators can analyze trends in product pricing&lt;/li&gt;
&lt;li&gt;Supporting related products to display on a product’s page&lt;/li&gt;
&lt;li&gt;Incorporating product vendors so customers can view all products sold by an individual vendor/company&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To address those extra requirements, you might end up with the following data model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FMore%2520complex%2520e-commerce%2520product%2520data%2520model.png%3Fwidth%3D1224%26name%3DMore%2520complex%2520e-commerce%2520product%2520data%2520model.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FMore%2520complex%2520e-commerce%2520product%2520data%2520model.png%3Fwidth%3D1224%26name%3DMore%2520complex%2520e-commerce%2520product%2520data%2520model.png" alt="More complex e-commerce product data model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This model still isn’t perfect as it embeds your prices into the product itself, but at least it lets you maintain a previous pricing history.&lt;/p&gt;

&lt;p&gt;Another option is to integrate your e-commerce store with a &lt;a href="https://resources.fabric.inc/glossary/promotions-engine" rel="noopener noreferrer"&gt;pricing and promotions engine&lt;/a&gt; from an API-first software provider that handles pricing for you. This will let you roll out different prices to different users based on their intent, location, cart, or order history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deep Dive: Pricing
&lt;/h3&gt;

&lt;p&gt;While the more complex product data model still has a product’s price in the same table, this may not be the best thing to do in a real large-scale application.&lt;/p&gt;

&lt;p&gt;Consider that your organization has various departments, such as inventory/warehousing, sales, marketing, customer support, etc. You might have dedicated systems that allow merchandisers to change the price of an item since they are the experts in determining how much a product should sell for. Similar to the considerations with a customer’s billing and shipping addresses, this would lead to cross-boundary/service communication if we left the price in the core &lt;code&gt;Product&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;Therefore, you might want to store product prices under the data stores that the sales department owns. But don’t forget, there are many different kinds of “prices” that haven’t been taken into consideration yet, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Price (cost) when purchasing stock from vendors&lt;/li&gt;
&lt;li&gt;Customer sale price&lt;/li&gt;
&lt;li&gt;Discounted sale prices&lt;/li&gt;
&lt;li&gt;Manufacturer’s suggested retail price&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Handling all these in context of your organizational structure would require even more exploration and complexity in your data model. While your engineering team could likely accomplish this task, it’s going to take time. Using ready-made solutions can shave weeks or months off your e-commerce data modeling timeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Streamline Orders?
&lt;/h2&gt;

&lt;p&gt;Now that you have customers in your database and products available to purchase, you’ll need to think about how to design the order-taking process and data model.&lt;/p&gt;

&lt;p&gt;The process of placing an order might look something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A customer places products into their cart while browsing.&lt;/li&gt;
&lt;li&gt;The customer decides they want to purchase the products that are in their cart.&lt;/li&gt;
&lt;li&gt;They proceed to purchase the order.&lt;/li&gt;
&lt;li&gt;The customer gets an emailed receipt or confirmation number.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, it’s rarely so simple. Placing orders can be deceptively tricky as there are many moving parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Products&lt;/li&gt;
&lt;li&gt;An active cart&lt;/li&gt;
&lt;li&gt;Cart converted into an order&lt;/li&gt;
&lt;li&gt;A finalized order with confirmation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you were to look at a simple data model for an order placement, it might look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FOrders%2520data%2520model.png%3Fwidth%3D1256%26name%3DOrders%2520data%2520model.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FOrders%2520data%2520model.png%3Fwidth%3D1256%26name%3DOrders%2520data%2520model.png" alt="Orders data model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that each row in the &lt;code&gt;ShoppingCartItem&lt;/code&gt; table contains the “captured” price of the product. When the customer puts an item into their shopping cart should the price at that moment be “locked-in”? If so, for how long?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; How the price functions is a business requirement that would need to be discussed with your product owners, and so on, as mentioned in the "Deep Dive: Pricing" section earlier.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The same question applies to an unpaid order. If a customer has ordered a discounted item, should they be able to keep the promise of that discounted price forever until they pay? Or does it expire?&lt;/p&gt;

&lt;p&gt;Other questions to consider for an orders data model might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are you tracking analytics on orders?&lt;/li&gt;
&lt;li&gt;What happens if a customer returns a defective item?&lt;/li&gt;
&lt;li&gt;Should you handle shipping within the same data model or have a dedicated shipping context/schema?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With some of these concerns in mind, you might end up with a data model that looks more like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FMore%2520complex%2520orders%2520data%2520model.png%3Fwidth%3D1668%26name%3DMore%2520complex%2520orders%2520data%2520model.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FMore%2520complex%2520orders%2520data%2520model.png%3Fwidth%3D1668%26name%3DMore%2520complex%2520orders%2520data%2520model.png" alt="More complex orders data model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some things to take note of in this more complex orders model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ShoppingCartItem&lt;/code&gt; now supports an expiration date for a locked-in price.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ShoppingCartHistory&lt;/code&gt; tracks when items are added, removed, etc.&lt;/li&gt;
&lt;li&gt;An order item may be returned (this still does not handle cases where 1 out of X items of the same product are returned).&lt;/li&gt;
&lt;li&gt;An order may have multiple shipments (e.g. how Amazon will sometimes split an order up into multiple packages/shipments).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article also hasn’t even touched the surface of using alternative data storage methods like JSON documents or event sourcing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;To help you see how all the pieces fit together, here are all the diagrams shown together. I’ve removed a number of links/lines to the Customer table to increase readability:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FSummary%2520e-commerce%2520data%2520model.png%3Fwidth%3D2094%26name%3DSummary%2520e-commerce%2520data%2520model.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fresources.fabric.inc%2Fhs-fs%2Fhubfs%2FSummary%2520e-commerce%2520data%2520model.png%3Fwidth%3D2094%26name%3DSummary%2520e-commerce%2520data%2520model.png" alt="Summary e-commerce data model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I mentioned above, this article still doesn’t even cover many of the basics like payment processing and invoicing. Beyond the features covered here, you might eventually require more advanced features like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coupon codes&lt;/li&gt;
&lt;li&gt;Taxes&lt;/li&gt;
&lt;li&gt;Third-party integrations with OAuth providers, other retailers, or partners&lt;/li&gt;
&lt;li&gt;Shipment tracking notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building a data model for an e-commerce application, as you can see, is not so simple. What looks upfront to be a straightforward set of database tables is not so simple once you dig into real-world requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  There’s Another Way
&lt;/h3&gt;

&lt;p&gt;What if you could have more of these abilities out-of-the-box?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fabric.inc/solutions" rel="noopener noreferrer"&gt;Fabric&lt;/a&gt; is an all-in-one commerce platform that helps you do everything this article talked about, like manage customers, orders, and shipments. Most importantly, it is a microservices-based and API-first platform. This means you can choose the services you need and integrate them seamlessly with any other internal or external service.&lt;/p&gt;

&lt;p&gt;At a high level, Fabric's platform includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://api.fabric.inc/" rel="noopener noreferrer"&gt;full suite of e-commerce APIs&lt;/a&gt; to help you manage customers, orders, and so on&lt;/li&gt;
&lt;li&gt;Tools to help you &lt;a href="https://fabric.inc/pim" rel="noopener noreferrer"&gt;bulk load existing SKUs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://fabric.inc/offers" rel="noopener noreferrer"&gt;analytics tool&lt;/a&gt; that uses customer behaviors to offer customized promotions and pricing&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://fabric.inc/oms" rel="noopener noreferrer"&gt;order management tool&lt;/a&gt; that helps you forecast inventory fulfillment and automate payment processing&lt;/li&gt;
&lt;li&gt;And more!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://fabric.inc/solutions" rel="noopener noreferrer"&gt;Check out Fabric&lt;/a&gt; if those sound like abilities your e-commerce application may need.&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Using Micro UIs To Extend Legacy Web Applications</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Fri, 10 Jul 2020 03:25:53 +0000</pubDate>
      <link>https://forem.com/jamesmh/using-micro-uis-to-extend-legacy-web-applications-166</link>
      <guid>https://forem.com/jamesmh/using-micro-uis-to-extend-legacy-web-applications-166</guid>
      <description>&lt;p&gt;Many are coming to learn about micro UIs or &lt;a href="https://martinfowler.com/articles/micro-frontends.html" rel="noopener noreferrer"&gt;micro frontends&lt;/a&gt; (which is &lt;a href="http://udidahan.com/2012/06/23/ui-composition-techniques-for-correct-service-boundaries/" rel="noopener noreferrer"&gt;not a new thing&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://www.jamesmichaelhickey.com/microservices-architecture/" rel="noopener noreferrer"&gt;article about Microservices Architecture&lt;/a&gt;, it highlights a big issue with how many think about and design microservices. In most microservice designs, the UI layer isn’t considered as a part of the service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F430teyxgo54ji066cdlr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F430teyxgo54ji066cdlr.png" alt="Typical microservice design" width="583" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this design, you end up coupling the UI layer with all the services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Team Coupling
&lt;/h2&gt;

&lt;p&gt;By coupling the services to a single monolithic UI, you’ve essentially eliminated the largest benefit for using a microservice design:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;The ability for teams to be autonomous and deploy their work independently of other teams.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In other words, a microservice architecture enables loose coupling between teams.&lt;/p&gt;

&lt;p&gt;However, teams will &lt;em&gt;conflict&lt;/em&gt; at the UI layer when UI concerns aren’t addressed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgzxxdgvggoz3aoo7ssdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgzxxdgvggoz3aoo7ssdv.png" alt="Teams who own their services but are still coupled when building their UIs" width="707" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead, we want to use what the SOA world has been doing for years: &lt;em&gt;give each team the ability to own their UI.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk54ukbvogmbf0809eoze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk54ukbvogmbf0809eoze.png" alt="Microservices with own UI components" width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Micro UIs
&lt;/h2&gt;

&lt;p&gt;Micro UIs is an approach where each team or service will create the UI parts that they need for their features and functionality.&lt;/p&gt;

&lt;p&gt;Eventually, teams &lt;em&gt;will&lt;/em&gt; have to co-ordinate their efforts when integrating their work.&lt;/p&gt;

&lt;p&gt;Especially on user-facing web pages, we will have the need to display functionality from multiple teams at the same time. &lt;em&gt;This is un-avoidable.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;However, the amount of coupling is minimal.&lt;/p&gt;

&lt;p&gt;The approach is to create a collection of individual components per service. These components are then composed together in the UI layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcbipd6lwavvnaokhn8b0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcbipd6lwavvnaokhn8b0.png" alt="Each component on the user-facing page is owned by a different service and team" width="552" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trouble With Legacy
&lt;/h2&gt;

&lt;p&gt;Many developers have to maintain existing legacy systems. On top of that, they have to continue to add new functionality too!&lt;/p&gt;

&lt;p&gt;These systems may have grown out-of-control due to a lack of design &amp;amp; architecture, planning, technical strategy or skillset of the team throughout the years.&lt;/p&gt;

&lt;p&gt;In any event, adding new functionality into the system can be &lt;em&gt;very hard.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are little to no automated tests: &lt;em&gt;how can you be sure you didn’t break any existing code?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Should you change the code that already exists?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Where do you even put the new code?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Chances are, those are difficult questions to answer since most of these legacy systems are very hard to reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Micro UIs To Extend Legacy Applications
&lt;/h2&gt;

&lt;p&gt;I’ve been using a technique that comes right out of the SOA world to enhance legacy systems.&lt;/p&gt;

&lt;p&gt;It does depend on the kind of change you need to add.&lt;/p&gt;

&lt;p&gt;If you need to add, let’s say, a new section of a screen or a new web page altogether then this might work for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case Study
&lt;/h2&gt;

&lt;p&gt;Here’s one example of where I’ve done this successfully.&lt;/p&gt;

&lt;p&gt;I was working on a system that had a combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User-facing VB.NET web application&lt;/li&gt;
&lt;li&gt;Internal VB.NET web application&lt;/li&gt;
&lt;li&gt;Public-facing product marketing web application&lt;/li&gt;
&lt;li&gt;.NET Framework web app used to render initial web pages&lt;/li&gt;
&lt;li&gt;100% custom-built JS framework with 100% custom-built components (using JQuery)&lt;/li&gt;
&lt;li&gt;.NET Web API that would interact with the custom-built JS framework to return JSON, HTML and everything in-between&lt;/li&gt;
&lt;li&gt;Various shared libraries between them&lt;/li&gt;
&lt;li&gt;One massive shared database behind everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are other systems that are missing. But this should give you an idea.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1o3zcz500cz6f9m0ahlp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1o3zcz500cz6f9m0ahlp.png" alt="architecture" width="693" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh ya, there were no automated tests. &lt;em&gt;Anywhere.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New Features Requested!
&lt;/h2&gt;

&lt;p&gt;As you can imagine, some existing features had logic spread across &lt;strong&gt;all&lt;/strong&gt; of these systems and much of it in the database too.&lt;/p&gt;

&lt;p&gt;The custom-built JS framework was also incredibly difficult to work with. What should have taken days to build was taking weeks and even up-to months of work.&lt;/p&gt;

&lt;p&gt;I decided that a new approach to building new features was worth trying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forming A Strategy
&lt;/h2&gt;

&lt;p&gt;In my mind, there were &lt;em&gt;many&lt;/em&gt; issues to address. Relevant to this article, here are the solutions to the most pressing problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stop using the custom-built JS framework because it’s dragging everything down with it&lt;/li&gt;
&lt;li&gt;Enable automated testing for all new features&lt;/li&gt;
&lt;li&gt;Encapsulate business/domain logic in a common area that can be shared by all the different parts of the system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Slowly, over time, I started applying domain-driven design approaches and techniques to improve things. This involved a lot of learning and having to train other developers.&lt;/p&gt;

&lt;p&gt;Here’s a rough sketch of how new features are designed from a conceptual look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqjpz3876m6luusqutozd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqjpz3876m6luusqutozd.png" alt="Design enables shared domain logic that is testable" width="584" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, that’s step one in a massive process of improving things.&lt;/p&gt;

&lt;p&gt;This is all excluding a new mobile application and API that I’ve been leading 😅.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Integrate?
&lt;/h2&gt;

&lt;p&gt;Some existing features needed to be enhanced. Using this design, we can, as much as possible, direct all new code into the domain project. Existing features will just call into the domain project and use it as needed.&lt;/p&gt;

&lt;p&gt;By using refactoring techniques, we have been able to extract existing logic into the domain layer and improve existing functionality and add tests to them!&lt;/p&gt;

&lt;p&gt;But what about times when you have &lt;em&gt;entirely new features or products to build?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;This is where micro UIs come in.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of integrating directly into the existing legacy codebase, we can build as much as possible into:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The domain project (which includes using DDD approaches and testable code)&lt;/li&gt;
&lt;li&gt;Dedicated UI components that interact directly with the domain logic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s a diagram of what this logically looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxch8e1bgdbvfhzsm3496.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxch8e1bgdbvfhzsm3496.png" alt="New features can be built independently and in an isolated fashion" width="543" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In practice, it technically looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjayuvhd54zxxhk9lr2o5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjayuvhd54zxxhk9lr2o5.png" alt="Shared micro UI component comes from shared CDN" width="543" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, we tried to minimize adding more infrastructure and complexity.&lt;/p&gt;

&lt;p&gt;The data flow of each feature is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Vue.js components are on a shared CDN.&lt;/li&gt;
&lt;li&gt;These components send HTTP messages to the existing .NET MVC application.&lt;/li&gt;
&lt;li&gt;The MVC application is split up into feature folders. Each feature is essentially a “mini-app” like structure.&lt;/li&gt;
&lt;li&gt;Each HTTP handler simply passes the request through to a domain level command or query handler.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  About Bounded Contexts
&lt;/h2&gt;

&lt;p&gt;In this case, the core of each of the new features was placed into the same .NET library. This is working with a small team that manages all the systems.&lt;/p&gt;

&lt;p&gt;However, it’s worth noting that this is essentially an SOA-like approach, logically.&lt;/p&gt;

&lt;p&gt;Some of these new features can potentially be bounded contexts. If so, then they are perfect candidates for becoming a microservice (if ever warranted) or a module in a modular monolith.&lt;/p&gt;

&lt;p&gt;The diagram above looks very similar to the one at the beginning of this article about microservices that own their own UI. It’s logically the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  There’s No Recipe
&lt;/h2&gt;

&lt;p&gt;Every team is different. Every situation is different.&lt;/p&gt;

&lt;p&gt;Right now, I’m helping this team separate some of these concerns into separate modules acting as bounded contexts.&lt;/p&gt;

&lt;p&gt;But, with these large legacy systems, it’s a long process of iterative improvements step-by-step!&lt;/p&gt;

&lt;p&gt;You have to work with what you have and face the reality of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructural constraints&lt;/li&gt;
&lt;li&gt;Technological constraints&lt;/li&gt;
&lt;li&gt;Business constraints&lt;/li&gt;
&lt;li&gt;Developer skill constraints&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This takes some thinking and design skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benefits
&lt;/h2&gt;

&lt;p&gt;There’s no doubt though that everyone, especially the business folk, sees the benefit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Being able to build new features that took weeks/months in days is a huge win&lt;/li&gt;
&lt;li&gt;Able to apply automated testing to domain logic produces more predictable behaviours and allows safe refactoring&lt;/li&gt;
&lt;li&gt;Able to isolate changes to a cohesive area means less chance of errors breaking unrelated parts of the software&lt;/li&gt;
&lt;li&gt;Ability to integrate new technologies like Vue.js into ancient technologies enables a richer user experience and selling point when hiring new developers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Are you working on a legacy application that is really hard to deal with?&lt;/p&gt;

&lt;p&gt;Is the business wanting to build new features but, so far, the existing code and design are causing tons of drag?&lt;/p&gt;

&lt;p&gt;The techniques in this article might help.&lt;/p&gt;

&lt;p&gt;Sometimes, it can be immensely helpful and time-saving to have someone with expertise to help you build a strategy around these kinds of changes and approaches.&lt;/p&gt;

&lt;p&gt;If you could use help, then you can &lt;a href="https://www.jamesmichaelhickey.com" rel="noopener noreferrer"&gt;get in touch with me.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Till next time!&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Interview With Nate Barbettini: OAuth, GraphQL, Event Sourcing</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Wed, 25 Mar 2020 14:18:08 +0000</pubDate>
      <link>https://forem.com/jamesmh/interview-with-nate-barbettini-oauth-graphql-event-sourcing-1kb9</link>
      <guid>https://forem.com/jamesmh/interview-with-nate-barbettini-oauth-graphql-event-sourcing-1kb9</guid>
      <description>&lt;p&gt;Derek Comartin (&lt;a class="mentioned-user" href="https://dev.to/codeopinion"&gt;@codeopinion&lt;/a&gt;) and I interviewed &lt;a href="https://twitter.com/nbarbettini" rel="noopener noreferrer"&gt;Nate Barbettini&lt;/a&gt; about topics like OAuth and why it can be confusing, domain-driven design, graphQL, event sourcing and more!&lt;/p&gt;

&lt;p&gt;Nate is the co-founder and CTO of Cobbler and a former Microsoft MVP.  He previously worked at Okta as a developer advocate and is the author of the Little ASP.NET Core Book.&lt;/p&gt;

&lt;p&gt;We had lots of fun just chatting it up! Check it out if those topics interest you 👍.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/3HZpTjtrRdg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>podcast</category>
      <category>graphql</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Should I Use A Microservices Architecture?</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Tue, 25 Feb 2020 19:22:38 +0000</pubDate>
      <link>https://forem.com/jamesmh/should-i-use-a-microservices-architecture-4751</link>
      <guid>https://forem.com/jamesmh/should-i-use-a-microservices-architecture-4751</guid>
      <description>&lt;p&gt;Microservices is a huge topic. However, it’s not a very &lt;em&gt;precise&lt;/em&gt; topic.&lt;/p&gt;

&lt;p&gt;Martin Fowler agrees:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While there is no precise definition of this architectural style...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://martinfowler.com/articles/microservices.html" rel="noopener noreferrer"&gt;https://martinfowler.com/articles/microservices.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  A Sea Of Confusion
&lt;/h1&gt;

&lt;p&gt;Diving into the topic of microservices, you’ll quickly discover that there are many differing opinions.&lt;/p&gt;

&lt;p&gt;For example, Martin Fowler suggests that &lt;a href="https://martinfowler.com/bliki/MonolithFirst.html" rel="noopener noreferrer"&gt;you shouldn’t start a new system using a microservices architecture.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, his peer Stefan Tilkov suggests that &lt;a href="https://martinfowler.com/articles/dont-start-monolith.html" rel="noopener noreferrer"&gt;you should always start with a microservices architecture.&lt;/a&gt; 🤔&lt;/p&gt;

&lt;p&gt;Udi Dahan has believed that microservices, as defined, are &lt;a href="http://udidahan.com/2014/03/31/on-that-microservices-thing/" rel="noopener noreferrer"&gt;still too fine-grained and potentially lack critical concerns around real boundaries and UI considerations.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who should we believe?&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Shifting Sands
&lt;/h1&gt;

&lt;p&gt;Let’s face it: opinions in tech are always changing and are often not as black-and-white as we’d like.&lt;/p&gt;

&lt;p&gt;“Best practices” are often shifting every few years.&lt;/p&gt;

&lt;p&gt;Once we thought DRY was the best thing since sliced bread. Now we are told that sharing code can be one of the most damaging architectural decisions one could make.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What about microservices?&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Microservices And Distributed Systems
&lt;/h1&gt;

&lt;p&gt;Most coverage about microservices is shallow, vague and contradictory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sadly, however, there’s not much information that outlines what the microservice style is and how to do it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://martinfowler.com/articles/microservices.html" rel="noopener noreferrer"&gt;https://martinfowler.com/articles/microservices.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To highlight this fact (it’s a very important fact), let’s look at &lt;a href="https://www.infoq.com/articles/architecture-trends-2019/" rel="noopener noreferrer"&gt;InfoQ’s Architecture And Design Trends Report for 2019.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that microservices is in the “late majority” section. This means microservices are fairly common in the industry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu9qs9emyt7wqdn52x5y8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu9qs9emyt7wqdn52x5y8.png" alt="architecture and design report" width="800" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, look at the 2nd section labelled “Early Adopters.” See that one called &lt;strong&gt;&lt;em&gt;Correctly built distributed systems?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzr7kl5nqfr4qqoz2to98.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzr7kl5nqfr4qqoz2to98.png" alt="architecture and design report" width="800" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As an industry, in general, we’ve been &lt;em&gt;doing&lt;/em&gt; microservices, but &lt;em&gt;not well.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  When In Doubt, Clarify
&lt;/h1&gt;

&lt;p&gt;However, let’s look at whether you should use a microservices architecture as a general concept.&lt;/p&gt;

&lt;p&gt;We’ll begin by asking some fundamental questions about microservices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is a microservice?&lt;/li&gt;
&lt;li&gt;What is a microservices architecture?&lt;/li&gt;
&lt;li&gt;How many microservices should be in my architecture?&lt;/li&gt;
&lt;li&gt;How big should they be?&lt;/li&gt;
&lt;li&gt;Should my entire architecture be comprised of microservices in order to have a microservices architecture?&lt;/li&gt;
&lt;li&gt;What about macro-services? Is there such a thing?&lt;/li&gt;
&lt;li&gt;If there is, are they better or worse than microservices? Why?&lt;/li&gt;
&lt;li&gt;What about my UI? Should that still be one massive single-page application?&lt;/li&gt;
&lt;li&gt;Can I use an MVC based web framework and still be using microservices?&lt;/li&gt;
&lt;li&gt;How autonomous should they be?&lt;/li&gt;
&lt;li&gt;How does this all relate to the cloud?&lt;/li&gt;
&lt;li&gt;Is serverless a kind of microservice architecture?&lt;/li&gt;
&lt;li&gt;If I do have a server can I still have a microservices architecture?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These &lt;em&gt;might&lt;/em&gt; seem like simple questions. But once you dig a little further you’ll find that the answers are not so straightforward.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Is A Microservice?
&lt;/h1&gt;

&lt;p&gt;Martin Fowler recognizes that microservices don’t have a precise definition. However, he tries to clarify what a microservice might look like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...services are independently deployable and scalable, each service also provides a firm module boundary, even allowing for different services to be written in different programming languages. They can also be managed by different teams.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://martinfowler.com/articles/microservices.html" rel="noopener noreferrer"&gt;https://martinfowler.com/articles/microservices.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Given this definition, &lt;a href="https://dev.to/educative/how-to-design-a-web-application-software-architecture-101-188b#monolith-or-microservice"&gt;the typical microservices architecture you’ll see&lt;/a&gt; will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnubo3oiq9q2qr2x4pvm2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnubo3oiq9q2qr2x4pvm2.png" alt="typical microservices architecture" width="583" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each microservice can be deployed and scaled on its own. They are running as independent processes and communicate over the network. Each has its own database.&lt;/p&gt;

&lt;p&gt;Ok. Now, take this example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc27k0wo3gvsjohwp2pdu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc27k0wo3gvsjohwp2pdu.png" alt="Realistic Monolithic Architecture" width="591" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a common architecture you’ll find in real companies who’ve been making money for a while.&lt;/p&gt;

&lt;p&gt;Imagine that each of these monolithic applications handles separate areas like shipping, billing, etc.&lt;/p&gt;

&lt;p&gt;The glaring problem here is that the shared database can lead to all kinds of issues that are typically avoided via proper encapsulation.&lt;/p&gt;

&lt;p&gt;What some companies have done is &lt;em&gt;not&lt;/em&gt; re-architect their system to a collection of microservices, but applied proper encapsulation to their monoliths (via &lt;a href="https://www.martinfowler.com/bliki/BoundedContext.html" rel="noopener noreferrer"&gt;bounded contexts&lt;/a&gt; from domain-driven design).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fev2llyaglx9z5kpik8s4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fev2llyaglx9z5kpik8s4.png" alt="Properly Bounded Monoliths" width="591" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s The Point?
&lt;/h1&gt;

&lt;p&gt;I have a question that exposes the general notion of what microservices are supposed to be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;What’s the difference between the microservices diagram and the properly encapsulated monoliths diagram?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The only difference is that the microservices architecture diagram is &lt;em&gt;sharing the user interface across the entire system.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What I find strange is that in most articles and discussions about microservices &lt;em&gt;the issue of UI is hardly ever mentioned.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Given the typical definition and explanation of microservices, the difference between the encapsulated monolith and “microservice” is &lt;em&gt;not&lt;/em&gt; immediately evident.&lt;/p&gt;

&lt;p&gt;(What if we removed the UI from some of our monoliths, added a REST API to each and gave them all a shared UI? That sounds awfully similar to the typical microservices architecture diagram you’ll see…)&lt;/p&gt;

&lt;p&gt;After all, &lt;em&gt;the biggest reason for moving to microservices is to enable different cross-functional teams to fully own their own products.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This works &lt;em&gt;very&lt;/em&gt; well given the properly bounded monolithic approach:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frhcb4l3mhl8885n0vvfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frhcb4l3mhl8885n0vvfm.png" alt="Monoliths each owned by cross-functional teams" width="704" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But, the general “microservices architecture” falls short on this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fokzpmqcu5e2cf7wr3ed0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fokzpmqcu5e2cf7wr3ed0.png" alt="Typical microservices architecture ownership" width="707" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Who owns the UI? Everyone? No one? Create a new team?&lt;/p&gt;

&lt;h1&gt;
  
  
  Putting On Our Big Boy/Girl Architectural Pants
&lt;/h1&gt;

&lt;p&gt;The reality is that software systems are much more fluid and dynamic than we would like to think.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It’s harder than we’d like it to be.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Software architecture involves taking &lt;em&gt;many smaller ideas&lt;/em&gt; and putting them all together in a way that makes sense for your use case.&lt;/p&gt;

&lt;p&gt;It’s &lt;em&gt;not&lt;/em&gt; about &lt;a href="https://en.wikipedia.org/wiki/Law_of_the_instrument" rel="noopener noreferrer"&gt;taking one general idea and applying that to our entire system.&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  A Practical Example
&lt;/h1&gt;

&lt;p&gt;Imagine a business that has an existing legacy system.&lt;/p&gt;

&lt;p&gt;This monolithic system is generally pretty risky to change. The business demands a faster time-to-market for some new features.&lt;/p&gt;

&lt;p&gt;Should we rewrite the entire system as a collection of microservices?&lt;/p&gt;

&lt;p&gt;It would make more sense to add new functionality via focused and encapsulated services that own dedicated business functions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftov649j8wedv6ltmnyir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftov649j8wedv6ltmnyir.png" alt="Microservices add additional functionality to the overall system" width="786" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This would be more of an evolutionary approach to software architecture (keep reading).&lt;/p&gt;

&lt;h1&gt;
  
  
  Some Reminders And Side Notes
&lt;/h1&gt;

&lt;p&gt;Again, it’s just not so cut-and-dry. We have to &lt;strong&gt;&lt;em&gt;think&lt;/em&gt;&lt;/strong&gt; about our existing systems, where we want the business to be in the near future and apply techniques and approaches that allow our business to keep growing and making money.&lt;/p&gt;

&lt;p&gt;Wasting time rewriting your entire system as microservices might not work out so well (&lt;a href="https://segment.com/blog/goodbye-microservices/" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://medium.com/@steven.lemon182/why-our-team-cancelled-our-move-to-microservices-8fd87898d952" rel="noopener noreferrer"&gt;2&lt;/a&gt;, &lt;a href="https://itnext.io/microservices-c8b5dbdd58b8" rel="noopener noreferrer"&gt;3&lt;/a&gt;, &lt;a href="https://rclayton.silvrback.com/failing-at-microservices" rel="noopener noreferrer"&gt;4&lt;/a&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A poor team will always create a poor system – it’s very hard to tell if microservices reduce the mess in this case or make it worse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://martinfowler.com/articles/microservices.html" rel="noopener noreferrer"&gt;https://martinfowler.com/articles/microservices.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And don’t forget that most of us aren’t even building distributed systems correctly anyways:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Awareness of the “microservices” architectural style may be moving into the late majority, but the related themes of “correctly designing distributed systems”, and designing for reactivity and fault tolerance are not so far along the adoption curve.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.infoq.com/articles/architecture-trends-2019/" rel="noopener noreferrer"&gt;https://www.infoq.com/articles/architecture-trends-2019/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Evolutionary Architecture
&lt;/h1&gt;

&lt;p&gt;This fluid and more adaptive approach to software architecture is sometimes called &lt;em&gt;evolutionary architecture&lt;/em&gt;. This is, in my opinion, just a plain responsible approach to software architecture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx5yndi6rvzqgc9iglo4b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx5yndi6rvzqgc9iglo4b.png" alt="Evolutionary Architecture" width="800" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;InfoQ has this approach listed in the “early adopters” phase. This indicates that most companies are still just “hammering away” with microservices.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Evolutionary architecture is a broad principle that architecture is constantly changing. While related to Microservices, it’s not Microservices by another name. You could evolve towards or away from Microservices for example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.infoq.com/podcasts/refactoring-evolutionary-architecture/" rel="noopener noreferrer"&gt;https://www.infoq.com/podcasts/refactoring-evolutionary-architecture/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since 2011, evolutionary architecture has been in the “adopt” category of ThoughtWorks’ technology radar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;It’s not really that new.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In contrast to traditional up-front, heavy-weight enterprise architectural designs, we recommend adopting evolutionary architecture. It provides the benefits of enterprise architecture without the problems caused by trying to accurately predict the future… We advocate delaying decisions to the latest responsible moment, which might in fact be up-front for some decisions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.thoughtworks.com/radar/techniques/evolutionary-architecture" rel="noopener noreferrer"&gt;https://www.thoughtworks.com/radar/techniques/evolutionary-architecture&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds like doing responsible, reasonable and intelligent software architecture to me 😅.&lt;/p&gt;

&lt;p&gt;If this interests you, I’d recommend watching &lt;a href="https://www.infoq.com/interviews/randy-shoup-microservices/" rel="noopener noreferrer"&gt;this 16-minute discussion by Randy Shoup.&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Step zero is to take a real customer problem, something with real customer benefit, maybe a reimplementation of something you have or ideally some new piece of functionality that was hard to do in the monolith.&lt;/p&gt;

&lt;p&gt;Do that in the new way, first, and what you are trying to do there is to learn from mistakes you inevitably will make going through that first transition, right? You do it in a relatively safe way, but at the same time you do with real benefit at the end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.infoq.com/interviews/randy-shoup-microservices/" rel="noopener noreferrer"&gt;https://www.infoq.com/interviews/randy-shoup-microservices/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As Randy later points out, it’s perfectly fine to leave certain parts of a system untouched if they have no real reason to change.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There were still pages on this site that were on V2 architecture, simply because they continue to work, they got 100,000 hits a day, no big deal, and they were neither painful enough to migrate nor having the sufficient ROI to migrate. So, they just stayed and they happily stayed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.infoq.com/interviews/randy-shoup-microservices/" rel="noopener noreferrer"&gt;https://www.infoq.com/interviews/randy-shoup-microservices/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  About That UI Issue…
&lt;/h1&gt;

&lt;p&gt;You might be wondering about the shared UI that appears in most microservices architecture diagrams.&lt;/p&gt;

&lt;p&gt;Is there a way to fix that?&lt;/p&gt;

&lt;p&gt;Yes, there is. &lt;em&gt;But it’s not so simple as we’d like.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It boils down to allowing each microservice to own it’s UI components.&lt;/p&gt;

&lt;p&gt;This allows individual teams to own the entire stack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fa5dcu2fpe15uh829pclq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fa5dcu2fpe15uh829pclq.png" alt="Composite UI" width="482" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Composite UIs
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;What happens when we have a UI that needs to display data from multiple services?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The solution is to compose multiple UI components owned by those services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs8rsuwzd8uhinqr7404t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs8rsuwzd8uhinqr7404t.png" alt="Composite UIs" width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In many cases, composite UIs are much more complicated than the image above. But this is a starting point to help you understand where this approach leads.&lt;/p&gt;

&lt;h1&gt;
  
  
  Not The Microservices I Knew
&lt;/h1&gt;

&lt;p&gt;This isn’t what most people think of when they say “microservices.” Microservices usually don’t have the notion of having UI components that are owned by it.&lt;/p&gt;

&lt;p&gt;In discussing this issue, Udi Dahan uses an example of a product’s price. Udi points out the fact that specific services need to own their UI. Otherwise, we break encapsulation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this manner, no code outside the service boundary would know about the concept of the product price and thus could not end up coupled to it.&lt;/p&gt;

&lt;p&gt;...I don’t really see how a [microservice] could end up having this level of ownership of data.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://udidahan.com/2014/03/31/on-that-microservices-thing/" rel="noopener noreferrer"&gt;http://udidahan.com/2014/03/31/on-that-microservices-thing/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Size Doesn’t Matter… Or Does It?
&lt;/h1&gt;

&lt;p&gt;You’ll hear that microservices need to be around 1000 lines of code or less. At least, I’ve heard that one before.&lt;/p&gt;

&lt;p&gt;However, microservices need to own the data, behaviour and UI of a given business context.&lt;/p&gt;

&lt;p&gt;We, as developers or architects, shouldn’t get to magically decide how big our services should be. It’s 100% totally dependent on the business context we are modelling.&lt;/p&gt;

&lt;p&gt;In relation to the issues surrounding UI ownership, Udi Dahan points out:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You’ll tend to see services that aren’t all that small, and probably not so many of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://udidahan.com/2014/03/31/on-that-microservices-thing/" rel="noopener noreferrer"&gt;http://udidahan.com/2014/03/31/on-that-microservices-thing/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Udi is considered one of the most highly respected experts in building distributed systems. And he’s told us – years ago – &lt;em&gt;that we’re doing it wrong.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Microservices are potentially &lt;em&gt;too micro&lt;/em&gt; because they aren’t respecting the boundaries of real business contexts. Instead of focusing on &lt;em&gt;size&lt;/em&gt; we ought to focus on &lt;em&gt;proper boundaries&lt;/em&gt; (which is a topic for another day).&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Should you use a microservices architecture?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;You should do what makes sense for your business.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, your business might want to build new products that require a quick time-to-market.&lt;/p&gt;

&lt;p&gt;You could add those new products as entirely separate monoliths that communicate with each other via event messaging to keep them loosely coupled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fawtb66jx6u63zyylm8vz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fawtb66jx6u63zyylm8vz.png" alt="The new monolith is separated and encapsulated" width="768" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They could be built as microservices that integrate back into the existing system via HTTP REST calls or event messaging?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbwmvxlpueu1w86nav4ge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbwmvxlpueu1w86nav4ge.png" alt="Existing monolith enhanced with microservices" width="786" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perhaps there’s an existing off-the-shelf solution that exists which your company can purchase? You could build a custom API on top that will allow integration with the existing system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5k4lhqeqhl36o24rh26e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5k4lhqeqhl36o24rh26e.png" alt="Off-the-shelf solution with a custom facade for integration" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There &lt;em&gt;are&lt;/em&gt; cases that call for splitting an existing system up into isolated components. They &lt;em&gt;could&lt;/em&gt; be microservices.&lt;/p&gt;

&lt;p&gt;Or, you could use a &lt;a href="http://www.kamilgrzybek.com/design/modular-monolith-primer/" rel="noopener noreferrer"&gt;plug-in component-like system within a monolithic system.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(I’ve written about &lt;a href="https://www.blog.jamesmichaelhickey.com/How-To-Build-Modular-Monoliths-With-NETCore-Razor-Class-Libraries/" rel="noopener noreferrer"&gt;an approach to building modular monoliths using .NET Core.&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;We haven’t even talked about factors around team skill/competency, project management styles, organizational influences, etc. These are all factors that affect your software architecture!&lt;/p&gt;

&lt;p&gt;In conclusion:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It depends.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But, are there guidelines for smaller organizations? Absolutely. And again: the meta-point with all these things is “only solve problems that you actually have”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.infoq.com/interviews/randy-shoup-microservices/" rel="noopener noreferrer"&gt;https://www.infoq.com/interviews/randy-shoup-microservices/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Need Help?
&lt;/h1&gt;

&lt;p&gt;I help Saas businesses improve their overall agility and time-to-market of new products and features.&lt;/p&gt;

&lt;p&gt;I also offer guidance for teams using .NET Core who need help with building web APIs, framework migrations, improving development processes, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.jamesmichaelhickey.com" rel="noopener noreferrer"&gt;Get in touch!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, check out &lt;a href="https://leanpub.com/refactoringtypescript" rel="noopener noreferrer"&gt;my book about keeping your code healthy!&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Keep In Touch
&lt;/h1&gt;

&lt;p&gt;Don't forget to connect with me on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/jamesmh_dev" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/jamesmhickey/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing The Loosely Coupled Show: Architecture And Design Video Chat / Podcast</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Fri, 21 Feb 2020 16:03:23 +0000</pubDate>
      <link>https://forem.com/jamesmh/introducing-the-loosely-coupled-show-architecture-and-design-video-chat-podcast-je</link>
      <guid>https://forem.com/jamesmh/introducing-the-loosely-coupled-show-architecture-and-design-video-chat-podcast-je</guid>
      <description>&lt;p&gt;Are you an intermediate or senior web developer? &lt;/p&gt;

&lt;p&gt;Do you recognize that learning about more higher-level and advanced design topics around structuring your software systems, software design, distributed systems, etc. are important and are required if you want to level-up in your career?&lt;/p&gt;

&lt;p&gt;For example, our industry is quickly migrating all web-based software to "the cloud."&lt;/p&gt;

&lt;p&gt;Do you know how to take advantage of the cloud? How would you even begin to start thinking about how to migrate an existing system to the cloud? Or design a new system well?&lt;/p&gt;

&lt;p&gt;Derek Comartin (&lt;a class="mentioned-user" href="https://dev.to/codeopinion"&gt;@codeopinion&lt;/a&gt;) and I have started a new video chat / podcast that will help introduce these topics in a conversational format.&lt;/p&gt;

&lt;p&gt;We'll have friendly chats about these topics, talk about our own experiences, maybe do some mock design use-cases and, of course, will chat with recognized industry experts from time-to-time.&lt;/p&gt;

&lt;p&gt;Check out our first introductory episode, like it and subscribe if you're interested in joining us!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/channel/UCNX9EQV4aEfa6fa9o6qcdEQ" rel="noopener noreferrer"&gt;Check it out here!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>podcast</category>
    </item>
    <item>
      <title>Modular Monoliths And Composite UIs With .NET Core Razor Class Libraries</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Tue, 10 Dec 2019 05:06:10 +0000</pubDate>
      <link>https://forem.com/jamesmh/modular-monoliths-and-composite-uis-with-net-core-razor-class-libraries-2394</link>
      <guid>https://forem.com/jamesmh/modular-monoliths-and-composite-uis-with-net-core-razor-class-libraries-2394</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;a href="https://www.blog.jamesmichaelhickey.com/How-To-Build-Modular-Monoliths-With-NETCore-Razor-Class-Libraries/" rel="noopener noreferrer"&gt;Originally published on my blog.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microservices are all the rage now.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;But, many are (finally) realizing that it's not for everyone. Most of us aren't at the scale of Netflix, LinkedIn, etc. and therefore don't have the organizational manpower to offset the overhead of a full-blown microservices architecture.&lt;/p&gt;

&lt;p&gt;An alternative to a full-blown microservices architecture that's been getting a lot of press lately is called "modular monoliths."&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Shouldn't well-written monoliths be modular anyways?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sure. But modular monoliths are done in a &lt;strong&gt;very intentional&lt;/strong&gt; way that usually follows a domain-driven approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary / Table Of Contents
&lt;/h2&gt;

&lt;p&gt;This is part of the 2019 C# Advent! &lt;a href="https://crosscuttingconcerns.com/The-Third-Annual-csharp-Advent" rel="noopener noreferrer"&gt;Take a look at all the other awesome articles for this year!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the main sections in the article in case you would like to skip certain parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction / Why Modular Monoliths?&lt;/li&gt;
&lt;li&gt;Building A Modular Monolith With .NET Core Razor Class Libraries&lt;/li&gt;
&lt;li&gt;Building A Composite UI With Blazor/Razor Components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The GitHub repo for everything in this article &lt;a href="https://github.com/jamesmh/net-core-modular-monolith-razor-lib" rel="noopener noreferrer"&gt;is here&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Modular Monoliths?
&lt;/h2&gt;

&lt;p&gt;The biggest benefits that microservices give us (loose coupling, code ownership, etc.) can be had in a well-designed modular monolith. By leveraging the domain-driven concept of bounded contexts, we can treat each context as an isolated application. &lt;/p&gt;

&lt;p&gt;But, instead of hosting each context as an independent process (like with microservices), we can extract each bounded context as a module within a larger system or web of modules. &lt;/p&gt;

&lt;p&gt;For example, each module might be a .NET project/assembly. These assemblies would be combined at run-time and hosted within one main process.&lt;/p&gt;

&lt;p&gt;Each module would own everything from UI components, back-end business/domain logic to persistence.&lt;/p&gt;

&lt;p&gt;Compared to microservices, the benefits of modular monoliths include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In-memory communication between contexts are more performant and reliable than doing it on the network&lt;/li&gt;
&lt;li&gt;A much simpler deployment process for smaller teams&lt;/li&gt;
&lt;li&gt;Retain boundaries and ownership around specific contexts&lt;/li&gt;
&lt;li&gt;Simplified upgrades to contexts/services&lt;/li&gt;
&lt;li&gt;Modules are "ready" to be extracted as services if needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I see modular monoliths as a step within the potential evolution of a system's architecture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WdVAbsZa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/qt3uhl7hqzrbewpw787a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WdVAbsZa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/qt3uhl7hqzrbewpw787a.png" alt="architecture spectrum" width="521" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For domain-driven approaches, starting with a modular monolith might make the most sense and is definitely worth considering.&lt;/p&gt;

&lt;p&gt;Here's a more in-depth &lt;a href="https://www.kamilgrzybek.com/design/modular-monolith-primer/" rel="noopener noreferrer"&gt;primer on modular monoliths&lt;/a&gt; by Kamil Grzybek if you're interested.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Can I Build Them?
&lt;/h2&gt;

&lt;p&gt;There are many ways to build modular monoliths!&lt;/p&gt;

&lt;p&gt;For example, Kamil Grzybek has created &lt;a href="https://github.com/kgrzybek/modular-monolith-with-ddd" rel="noopener noreferrer"&gt;a production-ready modular monolith sample&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I personally prefer less separation within each bounded context / module, but his example is super detailed and worth looking at!&lt;/p&gt;

&lt;p&gt;And, of course, Kamil says it well in his repo's disclaimer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The architecture and implementation presented in this repository is one of the many ways to solve some problems&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article, we'll be looking at a much simpler approach to building modular monoliths. Again, the direction and implementation depends on your needs (business requirements, team experience, time-to-market required, etc.)&lt;/p&gt;

&lt;p&gt;We'll be looking at one way that's unique to .NET Core by using a new-ish feature of .NET Core called &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-3.0&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt;razor class libraries&lt;/a&gt;. Razor class libraries allow you to build entire UI pages, controllers and components inside of a sharable library! It's the approach I've used for &lt;a href="https://www.pro.coravel.net/" rel="noopener noreferrer"&gt;Coravel Pro&lt;/a&gt; and enables some exciting possibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Life Insurance Application
&lt;/h2&gt;

&lt;p&gt;I recently wrote &lt;a href="https://www.blog.jamesmichaelhickey.com/DDD-Use-Case-Life-Insurance-Platform/" rel="noopener noreferrer"&gt;an article&lt;/a&gt; about using some domain-driven approaches to think about and re-design an insurance selling platform I once worked on.&lt;/p&gt;

&lt;p&gt;To keep things simple for now, let's imagine we've determined two bounded contexts from this domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Insurance Application&lt;/li&gt;
&lt;li&gt;Medical Questionnaire&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The specific details are not so important since the remainder of this article will look at implementation and code.&lt;/p&gt;

&lt;p&gt;The structure of our solution will roughly look like the following - with a .NET Core web application as the host:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IPGW4KQ---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/lfbc3cdn6otd6jp2wust.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IPGW4KQ---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/lfbc3cdn6otd6jp2wust.png" alt="Modules" width="292" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Our Skeleton
&lt;/h2&gt;

&lt;p&gt;Let's implement the skeleton for our modular monolith and use razor class libraries as a way to implement our bounded contexts.&lt;/p&gt;

&lt;p&gt;First, create a new root host process:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet new webapp -o HostApp&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, we'll create our two modules as razor class libraries:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet new razorclasslib -o Modules/InsuranceApplication&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet new razorclasslib -o Modules/MedicalQuestions&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lkIocEoM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/jr215zg4o2ouf96mgwjt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lkIocEoM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/jr215zg4o2ouf96mgwjt.png" alt="Folder structure" width="281" height="113"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, we'll reference our modules from the host project.&lt;/p&gt;

&lt;p&gt;From within the host project:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet add reference ../Modules/InsuranceApplication/InsuranceApplication.csproj&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet add reference ../Modules/MedicalQuestions/MedicalQuestions.csproj&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Our First Module
&lt;/h2&gt;

&lt;p&gt;Navigate to the Insurance Application module at &lt;code&gt;Modules/InsuranceApplication&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your razor class library will have some sample Blazor files, &lt;code&gt;www&lt;/code&gt; folder, etc. You can remove all those generated files.&lt;/p&gt;

&lt;p&gt;Let's build a couple of Razor Pages that will be used as this bounded context's UI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet new page -o Areas/InsuranceApplication/Pages -n ContactInfo&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet new page -o Areas/InsuranceApplication/Pages -n InsuranceSelection&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You might have to go into your razor pages and adjust the generated namespaces. In my example, I changed them to &lt;code&gt;InsuranceApplication.Areas.InsuranceApplication.Pages&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Oops! It Doesn't Work!
&lt;/h2&gt;

&lt;p&gt;Navigate back to the &lt;code&gt;HostApp&lt;/code&gt; web project and try to build it. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;It will fail!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our razor class libraries are trying to use razor pages - which is a web function. This requires referencing the appropriate reference assemblies.&lt;/p&gt;

&lt;p&gt;Before .NET Core 3.0, we would have added a reference to some extra NuGet packages like &lt;code&gt;Microsoft.AspNetCore.Mvc&lt;/code&gt;, etc. As of .NET Core 3.0, many of these ASP related packages are actually included in the .NET Core SDK.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For some projects, this breaking change can cause issues and confusion! For more details, check out Andrew Lock's &lt;a href="https://andrewlock.net/converting-a-netstandard-2-library-to-netcore-3/" rel="noopener noreferrer"&gt;in-depth look at this issue.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, let's change the project files of our two razor class libraries to the following:&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;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk.Razor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

 &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;netcoreapp3.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;AddRazorSupportForMvc&amp;gt;&lt;/span&gt;True&lt;span class="nt"&gt;&amp;lt;/AddRazorSupportForMvc&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

 &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;FrameworkReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.AspNetCore.App"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Yes, I removed all the boilerplate Blazor code since the appropriate references are included in the &lt;code&gt;FrameworkReference&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  It's Alive!
&lt;/h2&gt;

&lt;p&gt;Go into the razor pages you created and add some dummy HTML.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@page
@model InsuranceApplication.Areas.InsuranceApplication.Pages.ContactInfoModel
@{
}

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Insurance Contact Info&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try running your host application and navigate to &lt;code&gt;/InsuranceApplication/ContactInfo&lt;/code&gt; and &lt;code&gt;/InsuranceApplication/InsuranceSelection&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both pages should display. Cool!&lt;/p&gt;

&lt;h2&gt;
  
  
  UI Flow: Beginning The Insurance Application
&lt;/h2&gt;

&lt;p&gt;Let's look at building out a basic flow between our two modules.&lt;/p&gt;

&lt;p&gt;First, in the &lt;code&gt;HostApp&lt;/code&gt; project, in your &lt;code&gt;Pages/Index.cshtml&lt;/code&gt; file add the following HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/InsuranceApplication/ContactInfo"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Begin Your Application!&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will give us a link to click from our home screen to begin the flow through our application logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VbUk3HY2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/uf21lwhozoph149amcsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VbUk3HY2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/uf21lwhozoph149amcsd.png" alt="Screen 1" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  UI Flow: Inside The Insurance Application Module
&lt;/h2&gt;

&lt;p&gt;In your &lt;code&gt;ContactInfo.cshtml&lt;/code&gt; file within the &lt;code&gt;InsuranceApplication&lt;/code&gt; project, insert the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@page
@model InsuranceApplication.Areas.InsuranceApplication.Pages.ContactInfoModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Insurance Contact Info&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Email:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"EmailAddress"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

 &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;ContactInfo.cshtml.cs&lt;/code&gt;, add the following:&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;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc.RazorPages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;InsuranceApplication.Areas.InsuranceApplication.Pages&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;class&lt;/span&gt; &lt;span class="nc"&gt;ContactInfoModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PageModel&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BindProperty&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;EmailAddress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnPostAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Email Address is &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmailAddress&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;RedirectToPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./InsuranceSelection"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will allow us to enter an email address and move to the next page in our flow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0V0BYioX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/9w0hlk02ysx4drvp0yse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0V0BYioX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/9w0hlk02ysx4drvp0yse.png" alt="Screen 2" width="437" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;InsuranceSelection.cshtml&lt;/code&gt; page - just add a link to keep things simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/MedicalQuestionnaire/Questions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Next&lt;span class="nt"&gt;&amp;lt;a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can imagine that this page would allow the user to select a specific insurance plan they want to apply for.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  UI Flow: Medical Questionnaire Module
&lt;/h2&gt;

&lt;p&gt;You might have noticed that we never created any razor pages in the Medical Questionnaire module. Let's do that now.&lt;/p&gt;

&lt;p&gt;Navigate to the root of the &lt;code&gt;MedicalQuestions&lt;/code&gt; project and enter the following from your terminal:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet new page -o Areas/MedicalQuestionnaire/Pages -n Questions&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Again, you might need to adjust the generated namespaces.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Within the &lt;code&gt;Questions.cshtml&lt;/code&gt; file, add a link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Medical Questionnaire&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Finish&lt;span class="nt"&gt;&amp;lt;a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try running your host project with &lt;code&gt;dotnet run&lt;/code&gt; and run through the entire UI!&lt;/p&gt;

&lt;h2&gt;
  
  
  Composite UIs With Blazor Components
&lt;/h2&gt;

&lt;p&gt;When working with these kinds of loosely coupled modules a question arises: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when we need to display information from multiple bounded contexts on the same screen?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually, we resort to building composite UIs. &lt;/p&gt;

&lt;p&gt;These are UIs where various parts of the UI are rendered and controlled by components owned by a specific bounded context, service or module.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3InrJdIs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/ab4ux3ntndlvikpyq4il.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3InrJdIs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/ab4ux3ntndlvikpyq4il.png" alt="Composite UI" width="552" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see in the image above, each component might be owned by a different back-end module and would be isolated from and loosely coupled to the other modules.&lt;/p&gt;

&lt;p&gt;Let's build a simple composite UI using some exciting new .NET Core 3.0 technologies!&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Blazor Components
&lt;/h3&gt;

&lt;p&gt;We need to configure our host application to support the new blazor/razor components.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the following script to &lt;code&gt;Pages/Shared/_Layout.cshtml&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_framework/blazor.server.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;In &lt;code&gt;Startup.cs&lt;/code&gt; in &lt;code&gt;ConfigureServices&lt;/code&gt; add:
&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="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddServerSideBlazor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;In the &lt;code&gt;Configure&lt;/code&gt; method:
&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="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&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;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapRazorPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapBlazorHub&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Add this one.&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Insurance Application Component
&lt;/h3&gt;

&lt;p&gt;Navigate to the &lt;code&gt;InsuranceApplication&lt;/code&gt; project's root directory and execute the following:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet new razorcomponent -o ./Components -n ApplicationDashboard&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Replace the contents of your new razor component with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@using Microsoft.AspNetCore.Components.Web

&lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Application Dashboard&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    Time: @currentTime
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"Update"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Update&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

@code {
    private string currentTime = DateTime.Now.ToString();

    private void Update()
    {
        currentTime = DateTime.Now.ToString();       
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Medical Questions Component
&lt;/h3&gt;

&lt;p&gt;Do the same steps within the &lt;code&gt;MedicalQuestions&lt;/code&gt; module to create a dummy razor component (but change the title of the component).&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting It Together
&lt;/h3&gt;

&lt;p&gt;Now, back in your host application project, within the &lt;code&gt;Pages/Index.cshtml&lt;/code&gt; page, add the following to the top of the file:&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;@page&lt;/span&gt;
&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;InsuranceApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Components&lt;/span&gt; &lt;span class="c1"&gt;// Add this.&lt;/span&gt;
&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;MedicalQuestions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Components&lt;/span&gt; &lt;span class="c1"&gt;// Add this.&lt;/span&gt;
&lt;span class="n"&gt;@model&lt;/span&gt; &lt;span class="n"&gt;IndexModel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the middle of your HTML page, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 @(await Html.RenderComponentAsync&lt;span class="nt"&gt;&amp;lt;ApplicationDashboard&amp;gt;&lt;/span&gt;(RenderMode.ServerPrerendered))
 &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 @(await Html.RenderComponentAsync&lt;span class="nt"&gt;&amp;lt;QuestionsDashboard&amp;gt;&lt;/span&gt;(RenderMode.ServerPrerendered))
 &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally... start your host project using &lt;code&gt;dotnet run&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--37-sr1nv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/dptkqybd9xvv6ctybgu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--37-sr1nv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/dptkqybd9xvv6ctybgu5.png" alt="Composite UI Screenshot" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any time one of the components needs to be changed, the host application will (most likely) not need to change. Both bounded contexts are still isolated from each other but our architecture allows us to be smart about how we display that information.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: There are other ways to build these types of components: &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-3.1" rel="noopener noreferrer"&gt;view components&lt;/a&gt;, &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-3.1" rel="noopener noreferrer"&gt;tag helpers&lt;/a&gt;, &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/th-components?view=aspnetcore-3.1" rel="noopener noreferrer"&gt;tag helper components&lt;/a&gt;, javascript components, etc.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Modules can be deployed as NuGet packages so that they ultimately can be managed by independent teams and be isolated as true modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each bounded context can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use its own isolated database&lt;/li&gt;
&lt;li&gt;Have its own self-contained business logic&lt;/li&gt;
&lt;li&gt;Communicate with other bounded contexts by using messaging and domain events&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;In terms of service/module interaction, any calls between modules could use a shared abstractions library which would be configured in DI at run-time to use the concrete service from the appropriate bounded context:&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bnCnL1QE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/zjqq7t4y7zuxz51sk1l9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bnCnL1QE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/zjqq7t4y7zuxz51sk1l9.png" alt="References" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It doesn't show on the diagram, but the Medical Questions context might need to get some information from the Insurance Application context in some scenarios where using messaging isn't appropriate.&lt;/p&gt;

&lt;p&gt;In this case, the Medical Questions code would use the &lt;code&gt;IInsuranceApplicationService&lt;/code&gt; interface (from DI) to make those calls.&lt;/p&gt;

&lt;p&gt;Then, at run-time, the host process would configure the concrete implementation (&lt;code&gt;InsuranceApplicationFacade&lt;/code&gt;) to be given to anyone who asks for the interface.&lt;/p&gt;

&lt;p&gt;This technique keeps each module loosely coupled and enforces a strict contract in terms of what one bounded context can be explicitly be "asked for".&lt;/p&gt;

&lt;p&gt;&lt;em&gt;However, some might say the need to make direct calls to another bounded context is a sign that the boundaries are incorrect...a topic for another day.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope you found this article helpful and informative. I'm sure you can see that razor class libraries are a really exciting feature of .NET Core that hasn't been talked about too much.&lt;/p&gt;

&lt;p&gt;This is one way to use them and for some cases it might work really well!&lt;/p&gt;

&lt;p&gt;Here are some resources about the topics we covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-3.1" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1" rel="noopener noreferrer"&gt;Razor Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-3.1" rel="noopener noreferrer"&gt;View Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=kbKxmEeuvc4" rel="noopener noreferrer"&gt;Modular Monoliths Talk By Simon Brown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jimmybogard.com/composite-uis-for-microservices-a-primer/" rel="noopener noreferrer"&gt;Composite UIs for Microservices - A Primer By Jimmy Bogard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/architect-microservice-container-applications/microservice-based-composite-ui-shape-layout" rel="noopener noreferrer"&gt;Microsoft: Creating composite UI based on microservices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  My Book
&lt;/h1&gt;

&lt;p&gt;Check out my book about keeping your code healthy!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://leanpub.com/refactoringtypescript" rel="noopener noreferrer"&gt;&lt;img src="/img/refactoringts.png" alt="Refactoring TypeScript book"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Keep In Touch
&lt;/h1&gt;

&lt;p&gt;Don't forget to connect with me on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/jamesmh_dev" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/jamesmhickey/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ddd</category>
      <category>architecture</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Refactoring TypeScript: Black Friday Discount 👌</title>
      <dc:creator>James Hickey</dc:creator>
      <pubDate>Thu, 28 Nov 2019 02:28:44 +0000</pubDate>
      <link>https://forem.com/jamesmh/refactoring-typescript-black-friday-discount-3ki0</link>
      <guid>https://forem.com/jamesmh/refactoring-typescript-black-friday-discount-3ki0</guid>
      <description>&lt;p&gt;My book &lt;em&gt;Refactoring TypeScript: Keeping Your Code Healthy&lt;/em&gt; is on sale until Cyber Monday (Dec. 2, 2019) on Leanpub!&lt;/p&gt;

&lt;p&gt;Click on the image to get the coupon/discount:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://leanpub.com/refactoringtypescript/c/LplouncF7Kd7" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N1gZQpHK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/anxm8ony1r8y45uqqcju.png" alt="Refactoring TypeScript book" width="400" height="518"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can grab one book for $4.99 or 5 books for $13.99 (that would be great for a dev team, club, etc.)&lt;/p&gt;

&lt;p&gt;For some samples of the book, you can check out these articles which are excerpts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/jamesmh/why-should-you-refactor-your-code-53fd"&gt;Why Should You Refactor Your Code?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/jamesmh/unhealthy-code-null-checks-everywhere-2720"&gt;Unhealthy Code: Null Checks Everywhere!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/jamesmh/unhealthy-code-primitive-overuse-7mh"&gt;Unhealthy Code: Primitive Overuse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
