<?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: Christian Zink</title>
    <description>The latest articles on Forem by Christian Zink (@christianzink).</description>
    <link>https://forem.com/christianzink</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%2F603002%2Fae0d068d-eecb-4fca-a354-232512c634ba.jpg</url>
      <title>Forem: Christian Zink</title>
      <link>https://forem.com/christianzink</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/christianzink"/>
    <language>en</language>
    <item>
      <title>How to Integrate Endoflife.Date in Dependency-Track EoL</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Sun, 29 Mar 2026 10:19:48 +0000</pubDate>
      <link>https://forem.com/christianzink/how-to-integrate-endoflifedate-in-dependency-track-eol-2ho5</link>
      <guid>https://forem.com/christianzink/how-to-integrate-endoflifedate-in-dependency-track-eol-2ho5</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.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%2F5ktf4n6a27ic4f695edf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5ktf4n6a27ic4f695edf.png" alt=" " width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keeping your software up-to-date is crucial — but what happens when a library reaches &lt;strong&gt;end-of-life (EoL)&lt;/strong&gt;? It stops receiving security updates, leaving your applications exposed to hidden risks.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.dependencytrack.org/" rel="noopener noreferrer"&gt;OWASP Dependency-Track&lt;/a&gt;&lt;/strong&gt; is great for scanning SBOMs (Software Bill of Materials) for vulnerabilities/CVEs. But EoL dependencies, but EoL software may have unpatched vulnerabilities that aren’t reported — creating hidden risks.&lt;/p&gt;

&lt;p&gt;In this guide, I’ll show you &lt;strong&gt;how to set up&lt;/strong&gt; my experimental integration for Dependency-Track and &lt;strong&gt;start detecting EoL dependencies&lt;/strong&gt; from &lt;strong&gt;&lt;a href="https://endoflife.date" rel="noopener noreferrer"&gt;endoflife.date&lt;/a&gt;&lt;/strong&gt; in your projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Steps of this tutorial
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dependency-Track installation (You can skip this if you already have a running installation)&lt;/li&gt;
&lt;li&gt;Import SBOM (You can skip this if you already have a running installation)&lt;/li&gt;
&lt;li&gt;Get the Dependency-Track API key from the Web UI &lt;/li&gt;
&lt;li&gt;Install and run the integration&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Install Dependency-Track
&lt;/h2&gt;

&lt;p&gt;If you already have a running Dependency-Track installation, skip this step. Otherwise, the easiest way is via &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Download Docker Compose file&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://raw.githubusercontent.com/DependencyTrack/dependency-track/main/docker-compose.yml

&lt;span class="c"&gt;# Start Dependency-Track stack&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Once started, navigate to &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;&lt;code&gt;http://localhost:8080&lt;/code&gt;&lt;/a&gt; to access the web UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Import Your SBOM
&lt;/h2&gt;

&lt;p&gt;You need at least one SBOM loaded in Dependency-Track to analyze dependencies. In the web UI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Go to &lt;strong&gt;Projects → Add Project&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Upload your SBOM (Or use the &lt;a href="https://raw.githubusercontent.com/DependencyTrack/dependency-track/main/cyclonedx.json" rel="noopener noreferrer"&gt;example sbom&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt; Wait for the components to be processed&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;If you already have a project in Dependency-Track, you can skip this step.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fsgyiskmlm6fq5ldxzrri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fsgyiskmlm6fq5ldxzrri.png" alt=" " width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Get Your API Key
&lt;/h2&gt;

&lt;p&gt;The EoL integration uses the Dependency-Track API. To get your key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Log in to the &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;Web UI at http://localhost:8080&lt;/a&gt; (That's the default for Dependency-Track Installations)&lt;/li&gt;
&lt;li&gt; Click on your &lt;strong&gt;Administration → Access Management → Teams&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Generate a new key&lt;/li&gt;
&lt;li&gt; Copy it for later use&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxp03yjuq9veokz7gvwln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxp03yjuq9veokz7gvwln.png" alt=" " width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Install and Run the Integration
&lt;/h2&gt;

&lt;p&gt;You can choose Linux or Windows depending on your environment. &lt;/p&gt;

&lt;h3&gt;
  
  
  Linux
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Download the latest binary&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://github.com/Chriz76/endoflife-dependencytrack/releases/download/v0.1.0-alpha/eol-dt-linux-x64.tar.gz

&lt;span class="c"&gt;# Extract the archive&lt;/span&gt;
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzvf&lt;/span&gt; eol-dt-linux-x64.tar.gz

&lt;span class="c"&gt;# Make it executable&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x eol-dt

&lt;span class="c"&gt;# Run it&lt;/span&gt;
./eol-dt &lt;span class="nt"&gt;--apikey&lt;/span&gt; YOUR_DEPENDENCY_TRACK_API_KEY

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Windows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Download the &lt;a href="https://github.com/Chriz76/endoflife-dependencytrack/releases/download/v0.1.0-alpha/eol-dt-win-x64.zip" rel="noopener noreferrer"&gt;latest Windows binary&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Unzip it&lt;/li&gt;
&lt;li&gt;Run it with your api key
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eol-dt --apikey YOUR_DEPENDENCY_TRACK_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Review Results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  In the Dependency-Track UI, search for &lt;code&gt;"INT"&lt;/code&gt; to see flagged EoL components&lt;/li&gt;
&lt;li&gt;  Check the program output for details about matched components&lt;/li&gt;
&lt;li&gt;  Optionally, provide your own EoL dataset using the &lt;code&gt;--eoldata&lt;/code&gt; option&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F99oiahsi8euwckl8ub66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F99oiahsi8euwckl8ub66.png" alt=" " width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Notes &amp;amp; Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  This project is &lt;strong&gt;experimental&lt;/strong&gt; — use in test environments first&lt;/li&gt;
&lt;li&gt;  Matching relies on PURL, CPE, and name heuristics — some results may be incomplete&lt;/li&gt;
&lt;li&gt;  Future improvements: integrate more package repositories, combine EoL with CVE data, allow manual overrides&lt;/li&gt;
&lt;li&gt;For further options or feedback see the &lt;a href="https://github.com/Chriz76/endoflife-dependencytrack" rel="noopener noreferrer"&gt;endoflife-dependencytrack project on github&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Feedback &amp;amp; Contributions
&lt;/h2&gt;

&lt;p&gt;I’d love your feedback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Does this help detect hidden vulnerabilities?&lt;/li&gt;
&lt;li&gt;  Ideas to improve matching accuracy?&lt;/li&gt;
&lt;li&gt;  Found bugs or missing components?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>sbom</category>
      <category>dependencytrack</category>
    </item>
    <item>
      <title>How to use Redis and Lua Scripts in a C# ASP.NET Core Microservice Architecture</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Thu, 22 Jul 2021 20:23:33 +0000</pubDate>
      <link>https://forem.com/christianzink/how-to-use-redis-and-lua-scripts-in-a-c-asp-net-core-microservice-architecture-28ad</link>
      <guid>https://forem.com/christianzink/how-to-use-redis-and-lua-scripts-in-a-c-asp-net-core-microservice-architecture-28ad</guid>
      <description>&lt;p&gt;Use StackExchange.Redis and run Redis in Docker to Cache Aggregated Database Data for a Scaled C# Application&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%2Fcdn-images-1.medium.com%2Fmax%2F2722%2F1%2AaO2_ixUXznFFlGZoWuUtnw.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%2Fcdn-images-1.medium.com%2Fmax%2F2722%2F1%2AaO2_ixUXznFFlGZoWuUtnw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redis has many great use-cases&lt;/strong&gt; like session or full page &lt;strong&gt;caching&lt;/strong&gt;, &lt;strong&gt;queues&lt;/strong&gt;, &lt;strong&gt;pub/sub&lt;/strong&gt; and &lt;strong&gt;leaderboards/counting&lt;/strong&gt;, etc. Usable in your applications and microservice architectures.&lt;/p&gt;

&lt;p&gt;In this article, I show you how to use &lt;strong&gt;&lt;a href="https://stackexchange.github.io/StackExchange.Redis/" rel="noopener noreferrer"&gt;StackExchange.Redis&lt;/a&gt; in ASP.NET Core&lt;/strong&gt; to access a &lt;strong&gt;Redis server running in Docker&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://dev.to/christianzink/how-to-use-database-sharding-and-scale-an-asp-net-core-microservice-architecture-5h5l"&gt;example application&lt;/a&gt; lets users write posts in categories&lt;/strong&gt;. It uses Redis to &lt;strong&gt;cache aggregated data&lt;/strong&gt; for top categories and users while the &lt;strong&gt;database stores the item/line data as “source of truth”&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article starts with a &lt;strong&gt;simple first use-case&lt;/strong&gt; and then advances to a more &lt;strong&gt;complex one with Lua scripting&lt;/strong&gt; and &lt;strong&gt;the inbox pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contents
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Setup Redis in Docker and Prepare the .NET Core Project&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement the Top Categories&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Top Users, the Inbox Pattern, and Lua Scripting for Atomicity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final Thoughts and Outlook&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Setup Redis in Docker and Prepare the .NET Core Project
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install &lt;a href="https://hub.docker.com/editions/community/docker-ce-desktop-windows" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create the Redis container:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker run --name redis -d redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;a href="https://visualstudio.microsoft.com/en/vs/community/" rel="noopener noreferrer"&gt;Visual Studio Community&lt;/a&gt;&lt;/strong&gt; (it’s free) with the ASP.NET and web development workload. Open an ASP.NET Core 5 Web API project with EntityFramework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the following NuGet packages&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Microsoft.EntityFrameworkCore.Tools&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;StackExchange.Redis&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Create the following entities:&lt;/strong&gt;&lt;/p&gt;


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






&lt;h2&gt;
  
  
  2. Implement the Top Categories
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Add Top Categories
&lt;/h3&gt;

&lt;p&gt;Increment the count for a category when inserting a new post.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Line 1: Connect a Redis multiplexer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lines 7–11: Insert the post and update the category count in a transaction&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lines 13–15: Read the latest count for the category and add/update the entry in the Redis &lt;a href="https://redislabs.com/ebook/part-2-core-concepts/chapter-3-commands-in-redis/3-5-sorted-sets/" rel="noopener noreferrer"&gt;sorted set&lt;/a&gt; “&lt;em&gt;CategoriesByPostCount&lt;/em&gt;”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


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


&lt;blockquote&gt;
&lt;p&gt;The &lt;a href="https://redis.io/commands/ZADD" rel="noopener noreferrer"&gt;ZADD&lt;/a&gt; command has an optional &lt;em&gt;GT&lt;/em&gt; parameter: The value is only updated if it is greater than the current value. This prevents race conditions where another thread also increased and updated the count.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Read Top Categories
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;SortedSetRangeByRankWithScore&lt;/em&gt; is a wrapper for the Redis &lt;a href="https://redis.io/commands/zrange" rel="noopener noreferrer"&gt;ZRANGE&lt;/a&gt; command. It reads the category and the count. Use the &lt;em&gt;start&lt;/em&gt; and &lt;em&gt;end&lt;/em&gt; parameters for paging.&lt;/p&gt;


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





&lt;h2&gt;
  
  
  3. Top Users, the Inbox Pattern, and Lua Scripting for Atomicity
&lt;/h2&gt;

&lt;p&gt;For the users, I show you how to count the posts in Redis instead of the DB. This is e.g. needed in a sharded environment where the user’s posts are scattered over all shards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atomically Add Posts with Lua Scripting
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;PublishOutstandingPosts&lt;/em&gt; method sends new posts to Redis. It uses the inbox pattern and a &lt;a href="https://redislabs.com/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/" rel="noopener noreferrer"&gt;Lua script&lt;/a&gt; to make the operation “idempotent”.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/" rel="noopener noreferrer"&gt;Outbox, Inbox patterns and delivery guarantees explained&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Line 3–8: Prepare the Lua script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Line 13: Read the last sent post ID from Redis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Line 15: Load the unsent posts from the DB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Line 17–21: Execute the Lua script to add the post to the user&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Line 22: Set the post count for the user in &lt;em&gt;UsersByPostCount&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Line 25: Update the last sent ID after all operations are finished&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


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


&lt;p&gt;The Lua script tries to add the post ID and timestamp to the &lt;em&gt;PostsByTimestamp&lt;/em&gt; sorted set of the user.&lt;/p&gt;

&lt;p&gt;If it can add the key, it also increments the &lt;em&gt;PostCount&lt;/em&gt; of the user. The script makes the ZADD and ZINCRBY commands atomic. If the post ID already exists, it returns the existing &lt;em&gt;PostCount&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An extra counter per user is needed so that all key parameters map to the same &lt;a href="https://redislabs.com/blog/redis-clustering-best-practices-with-keys/" rel="noopener noreferrer"&gt;Redis hash tag&lt;/a&gt;. And therefore would be placed on the same Redis shard. The curly braces like in the key “{User:5}:PostsByTimestamp” are signifiers for a Redis hash tag.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Read the Top Users
&lt;/h3&gt;

&lt;p&gt;Use &lt;em&gt;SortedSetRangeByRankWithScore&lt;/em&gt; to read the IDs of the top users. Then read the names:&lt;/p&gt;


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





&lt;h2&gt;
  
  
  4. Final Thoughts and Outlook
&lt;/h2&gt;

&lt;p&gt;You used &lt;strong&gt;StackExchange.Redis to access Redis from C#&lt;/strong&gt;. See &lt;a href="https://dev.to/christianzink/how-to-cache-aggregated-data-with-redis-and-lua-scripts-for-a-scaled-microservice-architecture-4k3e"&gt;my previous article&lt;/a&gt; if you want to &lt;strong&gt;manually access Redis via redis-cli&lt;/strong&gt; or want &lt;strong&gt;more information about the example data model and use-cases&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The code in this article focuses on the Redis-related part. The &lt;strong&gt;basis for the code can be found in the solution from &lt;a href="https://dev.to/christianzink/how-to-use-database-sharding-and-scale-an-asp-net-core-microservice-architecture-5h5l"&gt;my article about database sharding&lt;/a&gt;&lt;/strong&gt; if you want to build and run the service.&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;many other use-cases for you to use Redis in your applications!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please contact me if you have any questions, ideas, or suggestions.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>redis</category>
      <category>microservices</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How to Cache Aggregated Data with Redis and Lua Scripts for a Scaled Microservice Architecture</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Wed, 30 Jun 2021 20:22:54 +0000</pubDate>
      <link>https://forem.com/christianzink/how-to-cache-aggregated-data-with-redis-and-lua-scripts-for-a-scaled-microservice-architecture-4k3e</link>
      <guid>https://forem.com/christianzink/how-to-cache-aggregated-data-with-redis-and-lua-scripts-for-a-scaled-microservice-architecture-4k3e</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fO0XRzUj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5u3o9ao5uebxdrlo96r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fO0XRzUj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5u3o9ao5uebxdrlo96r.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A scaled microservice-based application with a &lt;strong&gt;huge amount of growing data&lt;/strong&gt; has a &lt;strong&gt;challenge to effectively deliver aggregated data&lt;/strong&gt; like top lists. &lt;/p&gt;

&lt;p&gt;In this article, &lt;strong&gt;I show you how to use Redis to cache the aggregated data&lt;/strong&gt;. While the databases store the item/line data as “source of truth” and use sharding to scale. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A single Redis instance can handle some 100,000 operations per second&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My example data model with users, posts, and categories can be a basis for your own usecases. &lt;/p&gt;

&lt;h3&gt;
  
  
  Contents
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Example Use-Cases and Data Model&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setup Redis and Implement the Top Categories&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Top Users, Latest User Posts, and the Inbox Pattern&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lua Scripting for Atomicity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final Thoughts and Outlook&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Example Use-Cases and Data Model
&lt;/h2&gt;

&lt;p&gt;In the example microservice application users can write posts in categories. They can also read the posts by category including the author name. The newest posts are on top. The categories are fixed and change seldom.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See my previous post “&lt;a href="https://dev.to/christianzink/how-to-use-database-sharding-and-scale-an-asp-net-core-microservice-architecture-5h5l"&gt;How to use Database Sharding and Scale an ASP.NET Core Microservice Architecture&lt;/a&gt;” if you are interested in source code and more details about the example application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Logical Data Model:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MkTLJdaW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2ACyQDNhXl1RpHv14c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MkTLJdaW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2ACyQDNhXl1RpHv14c.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Currently, one million users exist. Every day each user writes about ten posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Top 10 Categories
&lt;/h3&gt;

&lt;p&gt;The top 10 categories will be displayed on the main page. This would require a statement like this for MySql:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT CategoryId, COUNT(PostId) FROM Post GROUP BY CategoryId ORDER BY COUNT(PostId) LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Executing this statement for millions of lines would be &lt;strong&gt;very slow&lt;/strong&gt;. And on every page visit, it would be &lt;strong&gt;impossible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because of the large amount of data, I also decided to shard by category. So it would require &lt;strong&gt;merging the top lists from multiple databases&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XHgVuG16--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2056/0%2Arp_9Zd0-TZyU1zE1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XHgVuG16--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2056/0%2Arp_9Zd0-TZyU1zE1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Setup Redis and Implement the Top Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install &lt;a href="https://hub.docker.com/editions/community/docker-ce-desktop-windows"&gt;Docker Desktop&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create the Redis container:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker run --name redis -d redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Connect to the container and start the redis-cli:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker exec -it redis redis-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Add Top Categories
&lt;/h3&gt;

&lt;p&gt;The the top categories (“&lt;em&gt;CategoriesByPostCount&lt;/em&gt;”) use a &lt;a href="https://redislabs.com/ebook/part-2-core-concepts/chapter-3-commands-in-redis/3-5-sorted-sets/"&gt;Redis sorted set&lt;/a&gt; (ZSET). &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add the first entry&lt;/strong&gt; with &lt;a href="https://redis.io/commands/ZADD"&gt;ZADD&lt;/a&gt; and 99 posts for the category “&lt;em&gt;Category5&lt;/em&gt;”:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1:6379&amp;gt; ZADD CategoriesByPostCount GT 99 "Category5"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;It adds one entry:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(integer) 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Add some more entries:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ZADD CategoriesByPostCount GT 1 "Category1"

(integer) 1

&amp;gt; ZADD CategoriesByPostCount GT 10 "Category2"

(integer) 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Update &lt;em&gt;Category5:&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ZADD CategoriesByPostCount GT 100 "Category5"

(integer) 1

&amp;gt; ZADD CategoriesByPostCount GT 98 "Category5"

(integer) 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The last command gives a result of zero. This happens because of the &lt;em&gt;GT&lt;/em&gt; parameter. The parameter helps to handle situations where updates arrive out-of-order (post counts don’t decrease). &lt;/p&gt;
&lt;h3&gt;
  
  
  Read Top Categories
&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://redis.io/commands/zrange"&gt;ZRANGE&lt;/a&gt; and read the top 10 categories with count of posts:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ZRANGE CategoriesByPostCount 0 9 WITHSCORES REV

1) "Category5"
2) "100"
3) "Category2"
4) "10"
5) "Category1"
6) "1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Easily retrieve the second page (entries 11–20), etc:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ZRANGE CategoriesByPostCount 10 19 WITHSCORES REV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;The posts per category can be counted in SQL when a new post is created:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEGIN TRANSACTION
INSERT INTO Post (...)
UPDATE Categories SET PostCount = PostCount + 1
COMMIT TRANSACTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This is possible because the database is sharded by category. &lt;strong&gt;All posts of one category are in the same database&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Top Users, Latest User Posts, and the Inbox Pattern
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;user’s posts are scattered over all shards&lt;/strong&gt;. It is not possible to use UPDATE User SET PostCount = PostCount + 1 and then update Redis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The operation in Redis has to be “idempotent”. The inbox pattern makes this possible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/"&gt;Outbox, Inbox patterns and delivery guarantees explained&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Add Posts (with a race condition)
&lt;/h3&gt;

&lt;p&gt;On every new post add add an entry to the  *PostsByTimestamp *sorted set of the user: &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ZADD {User:5}:PostsByTimestamp 3455667878 '{Title: "MyPostTitle", Category: "Category5", PostId: 13}'

(integer) 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then increment the post count in &lt;em&gt;UsersByPostCount&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ZINCRBY UsersByPostCount 1 "5"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;To make it idempotent check the result of adding the post to the inbox. Issuing the command again gives a result of zero (the entry already existed):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ZADD {User:5}:PostsByTimestamp 3455667878 '{Title: "MyPostTitle", Category: "Category5", PostId: 13}'

(integer) 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then don’t increment &lt;em&gt;UsersByPostCount.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The command ZADD to PostsByTimestamp and the command ZINCRBY to UsersByPostCount have to be atomic&lt;/strong&gt;. I will show you how to use a Redis Lua-Script to make it atomic. But first, let’s read the top users and latest user posts.&lt;/p&gt;
&lt;h3&gt;
  
  
  Read the Top Users and the Latest User Posts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Top 10 users:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ZRANGE UsersByPostCount 0 9 WITHSCORES REV

1) "6"
2) "10"
3) "5"
4) "8"
5) "3"
6) "4"
7) "1"
8) "3"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The user with ID 6 has 10 posts, ID 5 has 8 posts, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Top posts of the user with ID 5:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ZRANGE {User:5}:PostsByTimestamp 0 9 WITHSCORES REV

1) "{Title: \"MyPostTitle2\", Category: \"Category1\", PostId: 14}"
2) "3455667999"
3) "{Title: \"MyPostTitle\", Category: \"Category5\", PostId: 13}"
4) "3455667878"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Lua Scripting for Atomicity
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Atomically Add Posts with Lua Scripting
&lt;/h3&gt;

&lt;p&gt;A &lt;a href="https://redislabs.com/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/"&gt;Redis Lua script&lt;/a&gt; can make the command ZADD to PostsByTimestamp and the command ZINCRBY to UsersByPostCount atomic. But an extra counter per user is needed so that all key parameters map to the same &lt;a href="https://redislabs.com/blog/redis-clustering-best-practices-with-keys/"&gt;Redis hash tag&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The curly braces like in the key “{User:5}:PostsByTimestamp” are signifiers for a Redis hash tag. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This Lua script tries to add a key to a sorted set. If it can add the key, it also increments a counter. If the key already exists, it returns the value of the key:&lt;/p&gt;


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



&lt;p&gt;Use &lt;a href="https://redis.io/commands/eval"&gt;EVAL&lt;/a&gt; to call the Lua script and pass “&lt;em&gt;{User:8}:PostsByTimestamp&lt;/em&gt;” and “&lt;em&gt;{User:8}:PostCount&lt;/em&gt;” as keys (one line on the command line):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; EVAL "if tonumber(redis.call('ZADD', KEYS[1], ARGV[1], ARGV[2])) == 1 then return redis.call('INCR', KEYS[2]) else return redis.call('GET', KEYS[2]) end" 2 {User:8}:PostsByTimestamp {User:8}:PostCount 3455667999 "{Title: \"MyPostTitle2\", Category: \"Category1\", PostId: 14}"

(integer) 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then set the count for user 8 in &lt;em&gt;UsersByPostCount&lt;/em&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ZADD UsersByPostCount GT 1 "8"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Store the Script in Redis
&lt;/h3&gt;

&lt;p&gt;For performance reason you can store the sript in Redis:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; SCRIPT LOAD "if tonumber(redis.call('ZADD', KEYS[1], ARGV[1], ARGV[2])) == 1 then return redis.call('INCR', KEYS[2]) else return redis.call('GET', KEYS[2]) end"

"cd9222afab5eb8d579942016a8c22427eff99429"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Use the hash to call the script:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; EVALSHA "cd9222afab5eb8d579942016a8c22427eff99429" 2 {User:8}:PostsByTimestamp {User:8}:PostCount 4455667999 "{Title: \"MyPostTitle3\", Category: \"Category1\", PostId: 20}"

(integer) 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  5. Final Thoughts and Outlook
&lt;/h2&gt;

&lt;p&gt;In this article, you &lt;strong&gt;set up Redis&lt;/strong&gt; and started with a simple use-case to &lt;strong&gt;cache aggregated data&lt;/strong&gt;. Then you used the &lt;strong&gt;inbox pattern&lt;/strong&gt; and &lt;strong&gt;Lua scripting&lt;/strong&gt; for atomicity.&lt;/p&gt;

&lt;p&gt;In one of &lt;strong&gt;my next articles&lt;/strong&gt;, I will show you how to &lt;strong&gt;implement it in a C# ASP.NET Core microservice application&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redis offers much more&lt;/strong&gt; than I showed you in this article. You can &lt;strong&gt;explore the other commands and &lt;a href="https://redislabs.com/blog/5-industry-use-cases-for-redis-developers/"&gt;use-cases&lt;/a&gt;&lt;/strong&gt; how they solve problems in your application. In a real-life application, you might have to &lt;strong&gt;use TTL to automatically expire entries&lt;/strong&gt; so that the cache does not grow unlimited. Maybe you also need to &lt;strong&gt;scale Redis&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please contact me if you have any questions, ideas, or suggestions.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>database</category>
    </item>
    <item>
      <title>How to Scale an ASP.NET Core Microservice and Sharded Database. Load Test with JMeter</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Sun, 20 Jun 2021 20:31:17 +0000</pubDate>
      <link>https://forem.com/christianzink/how-to-scale-an-asp-net-core-microservice-and-sharded-database-load-test-with-jmeter-9mm</link>
      <guid>https://forem.com/christianzink/how-to-scale-an-asp-net-core-microservice-and-sharded-database-load-test-with-jmeter-9mm</guid>
      <description>&lt;h2&gt;
  
  
  Run multiple DBMS and C# Containers Behind an HAProxy Load Balancer with Docker Compose. Test Scaling with Different Number of Instances
&lt;/h2&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AzZry4z9DbP2JxzDun3gNIw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AzZry4z9DbP2JxzDun3gNIw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;&lt;a href="https://itnext.io/how-to-use-database-sharding-and-scale-an-asp-net-core-microservice-architecture-22c24916590f" rel="noopener noreferrer"&gt;previous article&lt;/a&gt;&lt;/strong&gt;, you created a &lt;strong&gt;microservice architecture&lt;/strong&gt; and manually implemented &lt;strong&gt;application-layer database sharding&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now&lt;/strong&gt;, you will &lt;strong&gt;scale the application&lt;/strong&gt; and &lt;strong&gt;run multiple container instances&lt;/strong&gt; of the microservice and databases. You will use &lt;strong&gt;Docker Compose&lt;/strong&gt; and an &lt;strong&gt;HAProxy load balancer&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AqfNgu11fAZHUkZeohvUzzA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AqfNgu11fAZHUkZeohvUzzA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you run &lt;strong&gt;JMeter load tests&lt;/strong&gt; to &lt;strong&gt;see how the application scales&lt;/strong&gt; when using a &lt;strong&gt;different number of instances&lt;/strong&gt;. Finally, you will also &lt;strong&gt;publish and receive messages from RabbitMQ&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Run Multiple Database and Microservice Instances
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dockerize the Microservice
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use the &lt;a href="https://itnext.io/how-to-use-database-sharding-and-scale-an-asp-net-core-microservice-architecture-22c24916590f" rel="noopener noreferrer"&gt;code and environment from the previous article&lt;/a&gt; as the basis&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rename the file “Dockerfile”&lt;/strong&gt; in the Visual Studio Project-Explorer to “&lt;em&gt;dockerfile&lt;/em&gt;” (first character lowercase). Then right-click the dockerfile and select “&lt;strong&gt;&lt;em&gt;Create Docker Image&lt;/em&gt;&lt;/strong&gt;”. This will also push the image to docker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the Application in Docker
&lt;/h3&gt;

&lt;p&gt;Create the file &lt;em&gt;docker-compose.yml&lt;/em&gt;:&lt;/p&gt;


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


&lt;p&gt;The docker file configures 3 database containers and 4 instances of the &lt;em&gt;Post&lt;/em&gt; service. Currently, only 2 databases are used by the &lt;em&gt;Post&lt;/em&gt; service instances. You can later remove the comment to use the third database. An HAProxy load balancer exposes the &lt;em&gt;Post&lt;/em&gt; service containers on port 5001.&lt;/p&gt;

&lt;p&gt;There is a 0.5 CPU limit for each container to help with a realistic load test on a local machine. On my 12 core notebook, there are still unused resources so that adding more service and database instances can bring a benefit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start the Application
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AAQEIZE2W6w_mMOoZVMumtA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AAQEIZE2W6w_mMOoZVMumtA.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Init the Databases
&lt;/h3&gt;

&lt;p&gt;Open your browser at &lt;a href="http://localhost:5001/swagger/index.html" rel="noopener noreferrer"&gt;http://localhost:5001/swagger/index.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Init the database with at least 100 users and 10 categories.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You could create more users and categories, but it will take some time because of the CPU limits.&lt;/p&gt;
&lt;/blockquote&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AfBl-wzNlTuKb9U2O9eJuPA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AfBl-wzNlTuKb9U2O9eJuPA.png"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Load Test the Scaled Application with JMeter
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Create the JMeter Test Plan
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Install&lt;/strong&gt; and open &lt;strong&gt;&lt;a href="https://jmeter.apache.org/download_jmeter.cgi" rel="noopener noreferrer"&gt;JMeter&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a test plan&lt;/strong&gt; and a thread group:&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%2Fcdn-images-1.medium.com%2Fmax%2F2118%2F1%2A2MOppF-OQDqtob6NQ6OnxQ.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%2Fcdn-images-1.medium.com%2Fmax%2F2118%2F1%2A2MOppF-OQDqtob6NQ6OnxQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;32 threads is a good number to start. On each loop of a thread, it adds one post and reads 10 posts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add an HTTP request to create a post:&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%2Fcdn-images-1.medium.com%2Fmax%2F2332%2F1%2ALZ7EcNWkotCzPOR07nJA-w.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%2Fcdn-images-1.medium.com%2Fmax%2F2332%2F1%2ALZ7EcNWkotCzPOR07nJA-w.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Servername: localhost&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Port: 5001&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTP-Request: POST&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Path: /api/Posts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Body Data:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    {
     "title": "MyTitle",
     "content": "MyContent",
     "userId": ${__Random(1,100)},
     "categoryId": "Category${__Random(1,10)}"
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It creates a post for a random user (ID 1–100) and category (1–10).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a Content-Type application/json HTTP header to the request:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2APU0P4sIvFow3KnatcuRs_g.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2APU0P4sIvFow3KnatcuRs_g.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read 10 posts of a random category:&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%2Fcdn-images-1.medium.com%2Fmax%2F2350%2F1%2Ashqj_R9K4-gKRXP4Bv0_NA.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%2Fcdn-images-1.medium.com%2Fmax%2F2350%2F1%2Ashqj_R9K4-gKRXP4Bv0_NA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Servername: localhost&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Port: 5001&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTP-Request: GET&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Path: /api/Posts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Send Parameters with the Request:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    NAME | VALUE | CONTENT-TYPE
    category | Category${__Random(1,10)} | text/plain
    count | 10 | text/plain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Run the Test
&lt;/h3&gt;

&lt;p&gt;Take a look at the &lt;em&gt;Summary Report&lt;/em&gt; while the test is running:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AroMDBtblo5M4cdddAupG-w.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AroMDBtblo5M4cdddAupG-w.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There should be no errors&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wait some time&lt;/strong&gt; until the values for the &lt;em&gt;average&lt;/em&gt; (response time) and &lt;em&gt;throughput&lt;/em&gt; get stable.&lt;/p&gt;
&lt;h3&gt;
  
  
  Modify the Test Parameters
&lt;/h3&gt;

&lt;p&gt;Stop the test in JMeter. &lt;/p&gt;

&lt;p&gt;You can &lt;strong&gt;change the threads&lt;/strong&gt; in the test plan. Rise them to e.g. 64 or 128 threads. Or decrease the threads to 16 or even 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shutdown the application&lt;/strong&gt; before you edit the &lt;em&gt;docker-compose.yml&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker-compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;You can &lt;strong&gt;change the number of &lt;em&gt;post&lt;/em&gt; service instances&lt;/strong&gt; via the “&lt;em&gt;scale&lt;/em&gt;” property. Change the “&lt;em&gt;environment&lt;/em&gt;” property for the number of databases (add/remove the comments):&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%2Fcdn-images-1.medium.com%2Fmax%2F2066%2F1%2AB2VytQ-0YZxOnRGzTIn7zw.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%2Fcdn-images-1.medium.com%2Fmax%2F2066%2F1%2AB2VytQ-0YZxOnRGzTIn7zw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start the application&lt;/strong&gt; after your changes:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;It takes some time until the database servers are running. And &lt;strong&gt;remember to initialize the database:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AfBl-wzNlTuKb9U2O9eJuPA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AfBl-wzNlTuKb9U2O9eJuPA.png"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Example Test Results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A ratio of two post services to one database gives good results on my computer.&lt;/strong&gt; I can scale it up to six services and three databases until I reach the limits of my hardware. The average time stays below 500ms. Increasing the threads higher than 64 produces errors.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The results depend on my environment and the CPU limitations. They will be different on your machine.&lt;/p&gt;
&lt;/blockquote&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AfFUuYLRlBc6dPKU8PGQSxA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AfFUuYLRlBc6dPKU8PGQSxA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The throughput per second is proportional to the number of 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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AzZry4z9DbP2JxzDun3gNIw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AzZry4z9DbP2JxzDun3gNIw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;305 requests per second with the CPU limited containers are about 25 million requests per day. That would allow 1 million users to write 10 posts a day and read posts. Of course, a real-life application will be more complex, but I hope the example shows the basic ideas.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. Inter Microservice Communication and Replicating User Changes
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;Post&lt;/em&gt; service receives changes to the users from the &lt;em&gt;User&lt;/em&gt; microservice via Rabbitmq messages:&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%2Fcdn-images-1.medium.com%2Fmax%2F2084%2F1%2AbmYSW4DUFtT0Gp4tI753MA.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%2Fcdn-images-1.medium.com%2Fmax%2F2084%2F1%2AbmYSW4DUFtT0Gp4tI753MA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will simulate that with JMeter, too.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create the RabbitMQ Container
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue the following command&lt;/strong&gt; (in one line in a console window) to start a RabbitMQ container with admin UI:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker run -d  -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=test -e RABBITMQ_DEFAULT_PASS=test --hostname my-rabbit --name some-rabbit rabbitmq:3-management
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The command configures “&lt;em&gt;test&lt;/em&gt;" as user and password instead of the default &lt;em&gt;Guest&lt;/em&gt; credentials. &lt;em&gt;Guest&lt;/em&gt; is limited to localhost, but inside docker, the containers are on different hosts. &lt;/p&gt;
&lt;h3&gt;
  
  
  Modify the Post Microservice
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Install the &lt;em&gt;RabbitMQ.Client&lt;/em&gt; NuGet package&lt;/strong&gt; in Visual Studio.&lt;/p&gt;

&lt;p&gt;Add the &lt;em&gt;IntegrationEventListenerService&lt;/em&gt; class:&lt;/p&gt;


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



&lt;p&gt;The background service uses the “&lt;em&gt;test&lt;/em&gt;" account to access RabbitMQ and &lt;em&gt;host.docker.internal&lt;/em&gt; and &lt;em&gt;localhost&lt;/em&gt; as hosts. This allows connections from inside a container and the Visual Studio debugger. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rabbitmq.com/consumers.html#single-active-consumer" rel="noopener noreferrer"&gt;Single active consumer&lt;/a&gt; guarantees that only one &lt;em&gt;Post&lt;/em&gt; service instance receives messages. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the active instance crashes then the next instance will take over. You can later try it by stopping the currently receiving instance in docker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The code creates the  exchange and pipe if they are not already existing. It uses manual acknowledgments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modify Startup.cs&lt;/strong&gt; to run the IntegrationEventListenerService:&lt;/p&gt;


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


&lt;h3&gt;
  
  
  Run the Changed Microservice in Docker
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Shutdown the application&lt;/strong&gt; in docker:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker-compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Build the &lt;em&gt;Post&lt;/em&gt; service, dockerize it and publish it to docker.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start the application&lt;/strong&gt; after your changes:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It takes some time until the database servers are running. And &lt;strong&gt;remember to initialize the database:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AfBl-wzNlTuKb9U2O9eJuPA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AfBl-wzNlTuKb9U2O9eJuPA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Modify the JMeter Tests
&lt;/h3&gt;

&lt;p&gt;In the JMeter test plan &lt;strong&gt;add a thread group with only one thread&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AcsXHQdi4ZTfjcuZ0yl1Xkw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AcsXHQdi4ZTfjcuZ0yl1Xkw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a constant timer:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AK8111jOJGfnuUIlzL7xTpg.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AK8111jOJGfnuUIlzL7xTpg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The constant timer limits the test to one message per second.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One message per second is still very often. Even if 1,000,000 users sign up  in half a year it would be only 4 new users per minute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Add the HTTP Request&lt;/strong&gt; to publish the message to RabbitMQ:&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%2Fcdn-images-1.medium.com%2Fmax%2F2368%2F1%2AjT78IqwvyI4rQeBPLhOhTg.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%2Fcdn-images-1.medium.com%2Fmax%2F2368%2F1%2AjT78IqwvyI4rQeBPLhOhTg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Servername: localhost&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Port: 15672&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTP-Request: POST&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Path: /api/exchanges/%2F/userloadtest/publish&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Body Data:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    {
     "properties":{},
     "routing_key":"user.update",
     "payload":"
     {
      id:1,name:\"NewName${__counter(TRUE,)}\",version:${__counter(TRUE,)}
     }",
     "payload_encoding":"string"
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user entity has a version field to handle out-of-order messages. To keep the test simple, it only updates a single user and increments the version field. The performance impact should stay the same. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add an HTTP Header Manager&lt;/strong&gt; for the &lt;em&gt;Content-Type&lt;/em&gt; and the authorization to RabbitMQ. &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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A_aIRJ2JqTYdaYVo8qr6PUg.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A_aIRJ2JqTYdaYVo8qr6PUg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Type | application/json
Authorization | Basic dGVzdDp0ZXN0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;“dGVzdDp0ZXN0" is the base64 encoded user and password “test".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Run the Tests
&lt;/h3&gt;

&lt;p&gt;The throughput should be similar to the previous tests. &lt;/p&gt;

&lt;p&gt;You can also identify the active RabbitMQ consumer in docker by looking at the console output. You can stop the container and another instance will take over.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Final Thoughts and Outlook
&lt;/h2&gt;

&lt;p&gt;You &lt;strong&gt;created a microservice architecture&lt;/strong&gt; and implemented application-layer &lt;strong&gt;database sharding&lt;/strong&gt;. Then you &lt;strong&gt;scaled the application with multiple container instances&lt;/strong&gt; and &lt;strong&gt;load tested&lt;/strong&gt; it. You also processed &lt;strong&gt;user change events from RabbitMQ&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;only an example application&lt;/strong&gt;. You will have to &lt;strong&gt;adjust the code to use it in a production environment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s next?&lt;/strong&gt; Getting data like the top 10 categories with the most posts requires querying multiple database instances. This might cause delays and kill performance. &lt;strong&gt;Redis can be a solution for querying aggregated data&lt;/strong&gt;. I will &lt;strong&gt;show it in one of my next posts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can also &lt;strong&gt;run the application in Kubernetes&lt;/strong&gt;. See &lt;strong&gt;my other articles:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://levelup.gitconnected.com/kubernetes-angular-asp-net-core-microservice-architecture-c46fc66ede44" rel="noopener noreferrer"&gt;How to Deploy your ASP.NET Core application to Kubernetes&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://levelup.gitconnected.com/kubernetes-angular-asp-net-core-microservice-architecture-c46fc66ede44" rel="noopener noreferrer"&gt;Use Angular for the UI&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Please contact me if you have any questions, ideas, or suggestions.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>jmeter</category>
      <category>dotnet</category>
      <category>microservices</category>
      <category>database</category>
    </item>
    <item>
      <title>How to use Database Sharding and Scale an  ASP.NET Core Microservice Architecture</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Sun, 13 Jun 2021 19:37:25 +0000</pubDate>
      <link>https://forem.com/christianzink/how-to-use-database-sharding-and-scale-an-asp-net-core-microservice-architecture-5h5l</link>
      <guid>https://forem.com/christianzink/how-to-use-database-sharding-and-scale-an-asp-net-core-microservice-architecture-5h5l</guid>
      <description>&lt;p&gt;Load Balance a C# ASP.NET Core Service and Use MySql App-Layer Sharding. Shows the Concepts, Which Also Apply to MongoDB, 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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AqfNgu11fAZHUkZeohvUzzA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AqfNgu11fAZHUkZeohvUzzA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the &lt;strong&gt;big advantages of microservices&lt;/strong&gt; is, that they can be &lt;strong&gt;scaled independently&lt;/strong&gt;. This article shows the &lt;strong&gt;benefits and challenges of scaling&lt;/strong&gt; one &lt;strong&gt;microservice and its database&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You will create a &lt;strong&gt;working example application&lt;/strong&gt; and manually &lt;strong&gt;implement application-layer sharding&lt;/strong&gt;. It shows how to &lt;strong&gt;choose a shard key&lt;/strong&gt; based on the use-cases and data model. This helps to apply the same principles to DBMS with integrated scaling like MongoDB, etc.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://dev.to/alex_barashkov/microservices-vs-monolith-architecture-4l1m"&gt;Microservices vs. Monolith Architecture&lt;/a&gt; and &lt;a href="https://dev.to/renaissanceengineer/database-sharding-explained-2021-database-scaling-tutorial-5cej"&gt;Database Sharding Explained- 2021 Database Scaling Tutorial&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the first of two parts. You will implement the microservice and use a sharded DB.&lt;/p&gt;

&lt;p&gt;In the second part, you will scale and run multiple container instances of the microservice and databases. You will use docker compose and a load balancer. Finally, you run JMeter load tests to see how the application scales when using a different number of instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Usecases and Datamodell
&lt;/h2&gt;

&lt;p&gt;The example application consists of a &lt;strong&gt;user and a post microservice&lt;/strong&gt;. They &lt;strong&gt;communicate via messages&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ADN2QzWP1oFpvFM0fh9gKrQ.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ADN2QzWP1oFpvFM0fh9gKrQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;See also my previous article &lt;a href="https://dev.to/christianzink/how-to-build-an-event-driven-asp-net-core-microservice-architecture-4fnh"&gt;How to Build an Event-Driven ASP.NET Core Microservice Architecture&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;User&lt;/em&gt; microservice handles adding and modifying users. The &lt;em&gt;Post&lt;/em&gt; microservices handles viewing and adding posts. There is far more interaction with the &lt;em&gt;Post&lt;/em&gt; microservice. So when the load to the app increases &lt;strong&gt;the &lt;em&gt;Post&lt;/em&gt; microservice will be the first microservice that needs to scale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The name of the author is part of the &lt;em&gt;PostService&lt;/em&gt; bounded context and therefore the &lt;em&gt;Post&lt;/em&gt; microservice. Adding and modifying authors is done in the &lt;em&gt;User&lt;/em&gt; microservice. The &lt;em&gt;User&lt;/em&gt; microservice sends events when a new user is added or a username changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logical Data Model of the &lt;em&gt;PostService&lt;/em&gt;
&lt;/h3&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AN-i4KoRko3WrIakewYLykQ.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AN-i4KoRko3WrIakewYLykQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Users can write posts in categories. They can also read the posts by category including the author name. Newest posts are on top. The categories are fixed and change seldom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Based on these use-cases I decided to shard by category:&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%2Fcdn-images-1.medium.com%2Fmax%2F2056%2F1%2AvBwiakd1uV_MnOKckNe-8A.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%2Fcdn-images-1.medium.com%2Fmax%2F2056%2F1%2AvBwiakd1uV_MnOKckNe-8A.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Implement the Microservice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install &lt;a href="https://visualstudio.microsoft.com/en/vs/community/" rel="noopener noreferrer"&gt;Visual Studio Community&lt;/a&gt;&lt;/strong&gt; (it’s free) with the ASP.NET and web development workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a solution&lt;/strong&gt; and add an ASP.NET Core 5 Web API project with the name “&lt;em&gt;PostService&lt;/em&gt;”. Disable HTTPS and activate OpenAPI Support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the following NuGet packages&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Microsoft.EntityFrameworkCore.Tools&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MySql.EntityFrameworkCore&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Newtonsoft.Json&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Create the Entities&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The index of the &lt;em&gt;Post&lt;/em&gt; entity should speed up the retrieval of the latest posts in a category:&lt;/p&gt;


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


&lt;p&gt;The Version in the &lt;em&gt;User&lt;/em&gt; entity will later help to handle out-of-order messages:&lt;/p&gt;


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



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


&lt;p&gt;&lt;strong&gt;Create the PostServiceContext&lt;/strong&gt;&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Add connection strings for the shards in appsettings.Development.json&lt;/strong&gt; (you will use two shards during debugging)&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Add the &lt;em&gt;DataAccess&lt;/em&gt; Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GetConnectionString(string category)&lt;/code&gt; calculates the hash of the CategoryId. The first part of the hash modulo the number of configured shards (connection strings) determines the shard for the given category.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;InitDatabase&lt;/em&gt; drops and recreates all tables in all shards and inserts dummy users and categories.&lt;/p&gt;

&lt;p&gt;The other methods create and load posts.&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Register DataAccess as a singleton in Startup.cs&lt;/strong&gt;&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Create the PostController&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It uses the &lt;em&gt;DataAccess&lt;/em&gt; class&lt;/p&gt;


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


&lt;h2&gt;
  
  
  3. Access a Database from the PostService
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install &lt;a href="https://hub.docker.com/editions/community/docker-ce-desktop-windows" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create two MySql Containers (each command as one line)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker run -p 3310:3306 --name=mysql1 -e MYSQL_ROOT_PASSWORD=pw -d mysql:5.6

C:\dev&amp;gt;docker run -p 3311:3306 --name=mysql2 -e MYSQL_ROOT_PASSWORD=pw -d mysql:5.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Start the &lt;em&gt;Post&lt;/em&gt; service in Visual Studio. The browser opens at &lt;a href="http://localhost:5001/swagger/index.html" rel="noopener noreferrer"&gt;http://localhost:5001/swagger/index.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the swagger UI to interact with the service:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Init the Databases with 100 users and 10 categories:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AfBl-wzNlTuKb9U2O9eJuPA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AfBl-wzNlTuKb9U2O9eJuPA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a post to “Category1”:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "title": "MyTitle",
  "content": "MyContent",
  "userId": 1,
  "categoryId": "Category1"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Read the top 10 posts&lt;/strong&gt; in “Category1” to see your new post:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AqrfNkc28IBcL-Lp7Qg3ONQ.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AqrfNkc28IBcL-Lp7Qg3ONQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect to the database containers and &lt;strong&gt;verify which database contains the new post&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker container exec -it mysql1 /bin/sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Login to MySql with the password “pw” and read the posts:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Adi3f8KF_WZBu0NY59Mno8w.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Adi3f8KF_WZBu0NY59Mno8w.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second instance does not contain any post:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker container exec -it mysql2 /bin/sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AGVUXZpvsfZIjDcSTr8JM2Q.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AGVUXZpvsfZIjDcSTr8JM2Q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Final Thoughts and Outlook
&lt;/h2&gt;

&lt;p&gt;You created a &lt;strong&gt;working application and implemented application-layer sharding&lt;/strong&gt; and used the concept of shard keys.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/christianzink/how-to-scale-an-asp-net-core-microservice-and-sharded-database-load-test-with-jmeter-9mm"&gt;&lt;strong&gt;second part&lt;/strong&gt;&lt;/a&gt;, you will &lt;strong&gt;scale&lt;/strong&gt; and &lt;a href="https://dev.to/christianzink/how-to-scale-an-asp-net-core-microservice-and-sharded-database-load-test-with-jmeter-9mm"&gt;&lt;strong&gt;run multiple container instances&lt;/strong&gt;&lt;/a&gt; of the microservice and database. You will use &lt;strong&gt;docker compose&lt;/strong&gt; and a &lt;strong&gt;load balancer&lt;/strong&gt;. You will then run &lt;strong&gt;JMeter load tests&lt;/strong&gt; to see how the application scales when using a different number of instances. Finally, you will &lt;strong&gt;simulate user events&lt;/strong&gt; from the &lt;em&gt;User&lt;/em&gt; microservice via &lt;strong&gt;RabbitMQ&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please contact me if you have any questions, ideas, or suggestions.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>architecture</category>
      <category>dotnet</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Outbox Pattern in Event-Driven ASP.NET Core Microservice Architectures</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Wed, 26 May 2021 19:34:49 +0000</pubDate>
      <link>https://forem.com/christianzink/the-outbox-pattern-in-event-driven-asp-net-core-microservice-architectures-89</link>
      <guid>https://forem.com/christianzink/the-outbox-pattern-in-event-driven-asp-net-core-microservice-architectures-89</guid>
      <description>&lt;p&gt;How to Build a Resilient Architecture with RabbitMQ, C#, Entity Framework, and the Transactional Outbox Pattern&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%2Fcdn-images-1.medium.com%2Fmax%2F2852%2F1%2A2XzNHj4sM_IbYyo2hH-Tgw.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%2Fcdn-images-1.medium.com%2Fmax%2F2852%2F1%2A2XzNHj4sM_IbYyo2hH-Tgw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the first step, you will create &lt;strong&gt;two microservices&lt;/strong&gt;. Each microservice &lt;strong&gt;has its own database&lt;/strong&gt;. They use &lt;strong&gt;events to publish changes to a RabbitMQ event bus&lt;/strong&gt;. (You can skip this part if you already implemented it in my last article). Next, you will &lt;strong&gt;see how messages get lost&lt;/strong&gt; e.g. when the message bus is down.&lt;/p&gt;

&lt;p&gt;In the second part, you apply the &lt;strong&gt;&lt;a href="https://microservices.io/patterns/data/transactional-outbox.html" rel="noopener noreferrer"&gt;transactional outbox pattern&lt;/a&gt;&lt;/strong&gt; and see how it &lt;strong&gt;prevents losing messages&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the last step, you add publisher and subscriber &lt;strong&gt;acknowledgments&lt;/strong&gt; and &lt;strong&gt;duplicate/out-of-order message handling&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contents
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create the .NET Core Microservices and Exchange Messages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement the Transactional Outbox Pattern&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(Optional) Test the Implementation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add Publisher Notify, Acknowledgments, and Resilient Message Handling &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final Thoughts and Outlook&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Create the .NET Core Microservices and Exchange Messages
&lt;/h2&gt;

&lt;p&gt;See my previous article for &lt;a href="https://dev.to/christianzink/how-to-build-an-event-driven-asp-net-core-microservice-architecture-4fnh"&gt;how to create the Microservices and configure RabbitMQ&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;This will give you the following components and workflow:&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%2Fcdn-images-1.medium.com%2Fmax%2F2564%2F1%2AHdhCYNbYhiF4_Pt708ZCcg.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%2Fcdn-images-1.medium.com%2Fmax%2F2564%2F1%2AHdhCYNbYhiF4_Pt708ZCcg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now both microservices and the RabbitMQ container “some-rabbit” should be running. &lt;strong&gt;Use the REST API&lt;/strong&gt; of the User service to create and modify users and &lt;strong&gt;make sure everything is working&lt;/strong&gt;. The User service should be sending events to the event bus and the Post service handles them. And the users in the user database are in sync with the users in the post database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Messages Getting Lost
&lt;/h3&gt;

&lt;p&gt;Stop the RabbitMQ container:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker stop some-rabbit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Use the Swagger UI to create a user in the UserService:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "name": "Chris2",
 "mail": "chris2@chris2.com",
 "otherData": "Some other data"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The RabbitMQ server can’t be reached:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AWKlblqy35DDue5czDi43rg.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AWKlblqy35DDue5czDi43rg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The user is in the user database:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AI7-FOm_OoNMzl9d3mRo8qA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AI7-FOm_OoNMzl9d3mRo8qA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The event is lost and therefore the user is not in the post database and the microservices are now inconsistent.&lt;/strong&gt; In the next step of this guide, you will see how to solve this problem.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Implement the Transactional Outbox Pattern
&lt;/h2&gt;

&lt;p&gt;In this part of the guide, you will add the transactional outbox pattern to the UserService project to prevent lost messages.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If you want to read about the details and concepts of the transactional outbox pattern, you can get more information about it at: &lt;a href="https://microservices.io/patterns/data/transactional-outbox.html" rel="noopener noreferrer"&gt;https://microservices.io/patterns/data/transactional-outbox.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Create the IntegrationEvent entity:&lt;/strong&gt;&lt;/p&gt;


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



&lt;p&gt;Add it to the UserServiceContext:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public DbSet&amp;lt;UserService.Entities.IntegrationEvent&amp;gt; IntegrationEventOutbox { get; set; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Modify the UserController&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The code in PutUser and PostUser starts a transaction and updates/inserts the User entity. In the same transaction it inserts the IntegrationEvent in the database instead of directly publishing the event:&lt;/p&gt;


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



&lt;p&gt;&lt;strong&gt;Create the publisher as BackgroundService&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The IntegrationEventSenderService polls the database and sends all outstanding events to RabbitMQ:&lt;/p&gt;


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


&lt;blockquote&gt;
&lt;p&gt;Continuously polling the database is not good. You will improve the polling in the next step.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Add the IntegrationEventSenderService as HostedService to Startup.cs:&lt;/p&gt;


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


&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/multi-container-microservice-net-applications/background-tasks-with-ihostedservice" rel="noopener noreferrer"&gt;Implement background tasks in microservices with IHostedService and the BackgroundService class&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. (Optional) Test the Implementation
&lt;/h3&gt;

&lt;p&gt;Now it’s a good time to test if everything is working like expected:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete the user.db&lt;/strong&gt; so that the database schema is created including the outbox table&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete the post.db&lt;/strong&gt; so both databases are empty&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start the RabbitMQ container&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker start some-rabbit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Start the User Service&lt;/strong&gt; in Visual Studio&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the Swagger UI to create a user&lt;/strong&gt; in the UserService:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "name": "Chris",
 "mail": "chris@chris.com",
 "otherData": "Some other data"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The sending of the event is logged to the console:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AeEhRvp20AWEzhGIWgptryA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AeEhRvp20AWEzhGIWgptryA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop the RabbitMQ container&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker stop some-rabbit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Use the Swagger UI to create another user&lt;/strong&gt; in the UserService:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "name": "Chris2",
 "mail": "chris2@chris2.com",
 "otherData": "Some other data"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The IntegrationEventSender can’t send the message and logs a RabbitMQ exception every 5 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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Aa0p5DBdIihZnACjGo04A1w.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Aa0p5DBdIihZnACjGo04A1w.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start the PostService&lt;/strong&gt; (it has no automatic restarting logic)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start the RabbitMQ container&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker stop some-rabbit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The IntegrationEventSender sends the message:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Aa33Luv8n_d1Yk22hPQQxQw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Aa33Luv8n_d1Yk22hPQQxQw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Congratulations: Your outbox implementation is working like expected!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Add Publisher Notify, Acknowledgments, and Resilient Message Handling
&lt;/h2&gt;

&lt;p&gt;Inserting or updating the user entity &lt;strong&gt;notifies the publisher&lt;/strong&gt; so it &lt;strong&gt;loads data from the outbox table only if there are new entries&lt;/strong&gt;. A &lt;strong&gt;version field&lt;/strong&gt; for the user entity enables &lt;strong&gt;handling of duplicated or out-of-order messages&lt;/strong&gt; in the consumer. &lt;strong&gt;RabbitMQ publisher confirms, persistent messages and acknowledgments&lt;/strong&gt; handle situations where the publisher, subscriber, or the event-bus don’t work.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Detailed workflow of the outbox pattern, publisher notify, acknowledgments, and version control:&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%2Fcdn-images-1.medium.com%2Fmax%2F5328%2F1%2AOHnUE_VqIiI3bpdE05XgDQ.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%2Fcdn-images-1.medium.com%2Fmax%2F5328%2F1%2AOHnUE_VqIiI3bpdE05XgDQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modify the User entity&lt;/strong&gt; to include the version field:&lt;/p&gt;


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



&lt;blockquote&gt;
&lt;p&gt;You could use RowVersion with a production SQL database. But SQLite doesn’t support it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Modify the IntegrationEventSender&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It uses a CancelationToken to wake up when there are new entries in the outbox. See the comments in lines 47, 49, and 65 for how persistent messages and publisher confirms are implemented.&lt;/p&gt;


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


&lt;blockquote&gt;
&lt;p&gt;Synchronously waiting for publisher confirms is very inefficient. See the &lt;a href="https://www.rabbitmq.com/tutorials/tutorial-seven-dotnet.html" rel="noopener noreferrer"&gt;official RabbitMQ guide&lt;/a&gt; for how to improve it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Make sure, you created the queue as durable:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Durable queues will be recovered on node boot, including messages in them published as persistent. Messages published as transient will be discarded during recovery, even if they were stored in durable queues. (&lt;a href="https://www.rabbitmq.com/queues.html" rel="noopener noreferrer"&gt;https://www.rabbitmq.com/queues.html&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Modify the UserController&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set/increment the version field in PostUser/PutUser. &lt;/p&gt;

&lt;p&gt;Get the IntegrationEventSender in the constructor and call it after committing the transactions.&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Modify program.cs in the PostService project&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Line 78 disables automatic acknowledgments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Line 75 sends the acknowledgment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lines 45 and 64 check for duplicate messages&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


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


&lt;blockquote&gt;
&lt;p&gt;Using Program.Main is not optimal. You could move the code to a BackgroundService.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;You can now delete the user and post DB and test your implementation as you did in part three of this guide.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Final Thoughts and Outlook
&lt;/h2&gt;

&lt;p&gt;You added the outbox pattern to your event-driven architecture and made it more resilient.&lt;/p&gt;

&lt;p&gt;You would have to &lt;strong&gt;adjust the code to use it in a production environment&lt;/strong&gt;: &lt;strong&gt;Clean up the code&lt;/strong&gt; and &lt;strong&gt;apply security best practices.&lt;/strong&gt; Apply .NET Core design patterns, error handling, etc. You should also &lt;strong&gt;optimize the publisher acknowledgments&lt;/strong&gt; and &lt;strong&gt;use a BackgroundService in the post service&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;You could &lt;strong&gt;move the outbox logic to a central location&lt;/strong&gt; like the DbContext or &lt;strong&gt;use a framework like &lt;a href="https://masstransit-project.com/" rel="noopener noreferrer"&gt;MassTransit&lt;/a&gt;&lt;/strong&gt;. (These frameworks, &lt;strong&gt;SAGAs&lt;/strong&gt;, and &lt;strong&gt;scaling/sharding&lt;/strong&gt; might be good candidates for my next articles.)&lt;/p&gt;

&lt;p&gt;See my other articles on how to &lt;strong&gt;&lt;a href="https://dev.to/christianzink/how-to-build-an-asp-net-core-kubernetes-microservices-architecture-with-angular-on-local-docker-desktop-using-ingress-395n"&gt;deploy your ASP.NET Core application to Kubernetes, use Angular for the UI&lt;/a&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;a href="https://dev.to/christianzink/databases-in-a-kubernetes-angular-net-core-microservice-architecture-22jc"&gt;add MySql and MongoDB databases&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please contact me if you have any questions, ideas, or suggestions.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Build an Event-Driven ASP.NET Core Microservice Architecture with RabbitMQ and Entity Framework</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Sun, 16 May 2021 19:49:59 +0000</pubDate>
      <link>https://forem.com/christianzink/how-to-build-an-event-driven-asp-net-core-microservice-architecture-4fnh</link>
      <guid>https://forem.com/christianzink/how-to-build-an-event-driven-asp-net-core-microservice-architecture-4fnh</guid>
      <description>&lt;p&gt;Use RabbitMQ, C#, REST-API and Entity Framework for asynchronous decoupled communication and eventually consistency with integration events and publish-subscribe&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%2Fcdn-images-1.medium.com%2Fmax%2F2012%2F1%2AGLHalyO1jPRWeCp5Phs1iw.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%2Fcdn-images-1.medium.com%2Fmax%2F2012%2F1%2AGLHalyO1jPRWeCp5Phs1iw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this guide, you will &lt;strong&gt;create two C# ASP.NET Core Microservices&lt;/strong&gt;. Both microservices have their &lt;strong&gt;own bounded context and domain model&lt;/strong&gt;. Each microservice has its &lt;strong&gt;own database and REST API&lt;/strong&gt;. One microservice publishes &lt;strong&gt;integration events&lt;/strong&gt;, that the other microservice consumes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoupled Microservices — A Real-World Example With Code
&lt;/h2&gt;

&lt;p&gt;The application uses a real-world example with &lt;strong&gt;users that can write posts&lt;/strong&gt;. The user microservice allows creating and editing users. &lt;strong&gt;In the user domain, the user entity has several properties&lt;/strong&gt; like name, mail, etc. In the post domain, there is also a user so &lt;strong&gt;the post microservice can load posts and display the writers without accessing the user microservice&lt;/strong&gt;. The user entity in the post domain is much simpler:&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%2Fcdn-images-1.medium.com%2Fmax%2F2764%2F1%2ACgPKfmHdDLfSbosnCcR33g.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%2Fcdn-images-1.medium.com%2Fmax%2F2764%2F1%2ACgPKfmHdDLfSbosnCcR33g.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The microservices are &lt;strong&gt;decoupled&lt;/strong&gt; and the &lt;strong&gt;asynchronous communication&lt;/strong&gt; leads to &lt;strong&gt;eventual consistency&lt;/strong&gt;. This kind of architecture is the basis for &lt;strong&gt;loosely coupled services&lt;/strong&gt; and &lt;strong&gt;supports high scalability&lt;/strong&gt;. The microservices access their &lt;strong&gt;example Sqlite databases&lt;/strong&gt; via &lt;strong&gt;Entity Framework&lt;/strong&gt; and exchange messages via &lt;strong&gt;RabbitMQ&lt;/strong&gt; (e.g. on Docker Desktop).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Overview diagram of the workflow, components, and technologies:&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%2Fcdn-images-1.medium.com%2Fmax%2F2292%2F1%2AnBI724d-APCSGAYo5m3zcg.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%2Fcdn-images-1.medium.com%2Fmax%2F2292%2F1%2AnBI724d-APCSGAYo5m3zcg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code and configurations in this article are not suitable for production use. This guide focuses on the concepts and how the components interact. For this purpose error handling, etc. is omitted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Steps of this Guide
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create the .NET Core Microservices&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use RabbitMQ and Configure Exchanges and Pipelines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Publish and Consume Integration Events in the Microservices&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test the Workflow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final Thoughts and Outlook&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Create the .NET Core Microservices
&lt;/h2&gt;

&lt;p&gt;In the first part of this guide, you will &lt;strong&gt;create the User and Post Microservice&lt;/strong&gt;. You will &lt;strong&gt;add the Entities and basic Web APIs&lt;/strong&gt;. The entities will be &lt;strong&gt;stored and retrieved via Entity Framework&lt;/strong&gt; from &lt;strong&gt;Sqlite DBs&lt;/strong&gt;. Optionally you can test the User Microservice with the &lt;strong&gt;Swagger UI&lt;/strong&gt; in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let’s get started.
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Install &lt;a href="https://visualstudio.microsoft.com/en/vs/community/" rel="noopener noreferrer"&gt;Visual Studio Community&lt;/a&gt;&lt;/strong&gt; (it’s free) with the ASP.NET and web development workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a solution&lt;/strong&gt; and add the two ASP.NET Core 5 Web API projects “UserService” and “PostService”. Disable HTTPS and activate OpenAPI Support.&lt;/p&gt;

&lt;p&gt;For both projects &lt;strong&gt;install the following NuGet packages&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Microsoft.EntityFrameworkCore.Tools&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microsoft.EntityFrameworkCore.Sqlite&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RabbitMQ.Client&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implement the UserService
&lt;/h3&gt;

&lt;p&gt;Create the User Entity:&lt;/p&gt;


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


&lt;p&gt;Create the UserServiceContext:&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Edit Startup.cs&lt;/strong&gt; to configure the UserServiceContext to use Sqlite and call &lt;em&gt;Database.EnsureCreated()&lt;/em&gt; to make sure the database contains the entity schema:&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Create the UserController&lt;/strong&gt; (It implements only the methods necessary for this demo):&lt;/p&gt;


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


&lt;p&gt;Debug the UserService project and it will start your browser. You can use the swagger UI to test if creating and reading users is working:&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%2Fcdn-images-1.medium.com%2Fmax%2F2514%2F1%2ATufSfAX0F9Gs2xlrRgsZ2Q.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%2Fcdn-images-1.medium.com%2Fmax%2F2514%2F1%2ATufSfAX0F9Gs2xlrRgsZ2Q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implement the PostService
&lt;/h3&gt;

&lt;p&gt;Create the User and Post entities:&lt;/p&gt;


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



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


&lt;p&gt;Create the PostServiceContext:&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Edit startup.cs&lt;/strong&gt; to configure the UserServiceContext to use Sqlite and call &lt;em&gt;Database.EnsureCreated()&lt;/em&gt; to make sure the database contains the entity schema:&lt;/p&gt;


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


&lt;p&gt;Create the PostController:&lt;/p&gt;


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


&lt;p&gt;Currently, you can’t insert posts, because there are no users in the PostService database.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Use RabbitMQ and Configure Exchanges and Pipelines
&lt;/h2&gt;

&lt;p&gt;In the second part of this guide, you will &lt;strong&gt;get RabbitMQ running&lt;/strong&gt;. Then you will &lt;strong&gt;use the RabbitMQ admin web UI to configure the exchanges and pipelines&lt;/strong&gt; for the application. Optionally you can use the admin UI to send messages to RabbitMQ.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This graphic shows how the UserService publishes messages to RabbitMQ and the PostService and a potential other service consume those messages:&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%2Fcdn-images-1.medium.com%2Fmax%2F3872%2F1%2AK8oytI-Z7gw1Sp26CxGEng.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%2Fcdn-images-1.medium.com%2Fmax%2F3872%2F1%2AK8oytI-Z7gw1Sp26CxGEng.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The easiest way to get RabbitMQ running is to &lt;strong&gt;install &lt;a href="https://hub.docker.com/editions/community/docker-ce-desktop-windows" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/strong&gt;. Then &lt;strong&gt;issue the following command&lt;/strong&gt; (in one line in a console window) to start a RabbitMQ container with admin UI :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;docker run -d  -p 15672:15672 -p 5672:5672 --hostname my-rabbit --name some-rabbit rabbitmq:3-management
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Open your browser&lt;/strong&gt; on port 15672 and log in with the username “guest” and the password “guest”. Use the web UI to &lt;strong&gt;create an Exchange&lt;/strong&gt; with the name “user” of type “Fanout” and &lt;strong&gt;two queues&lt;/strong&gt; “user.postservice” and “user.otherservice”.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is important to use the type “Fanout” so that the exchange copies the message to all connected queues.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can also use the web UI to publish messages to the exchange and see how they get queued:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AYRD8fLl_NoNGRwLK7w6lww.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AYRD8fLl_NoNGRwLK7w6lww.png"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Publish and Consume Integration Events in the Microservices
&lt;/h2&gt;

&lt;p&gt;In this part of the guide, you will &lt;strong&gt;bring the .NET microservices and RabbitMQ together&lt;/strong&gt;. The &lt;strong&gt;UserService publishes&lt;/strong&gt; events. The &lt;strong&gt;PostService consumes&lt;/strong&gt; the events and &lt;strong&gt;adds/updates the users in its database&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modify UserService.UserController&lt;/strong&gt; to publish the integration events for user creation and update to RabbitMQ:&lt;/p&gt;


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



&lt;blockquote&gt;
&lt;p&gt;The connection and other RabbitMQ objects are not correctly closed in these examples. They should also be reused. See the &lt;a href="https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html" rel="noopener noreferrer"&gt;official RabbitMQ .NET tutorial&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Modify&lt;/strong&gt; (and &lt;em&gt;misuse&lt;/em&gt;) &lt;strong&gt;PostService.Program&lt;/strong&gt; to subscribe to the integration events and apply the changes to the PostService database:&lt;/p&gt;


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





&lt;h2&gt;
  
  
  4. Test the Workflow
&lt;/h2&gt;

&lt;p&gt;In the final part of this guide you will test the whole workflow:&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%2Fcdn-images-1.medium.com%2Fmax%2F2292%2F1%2AnBI724d-APCSGAYo5m3zcg.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%2Fcdn-images-1.medium.com%2Fmax%2F2292%2F1%2AnBI724d-APCSGAYo5m3zcg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary of the steps in the last part of this guide&lt;/strong&gt; (you can access the services with the Swagger UI):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Call the UserService REST API and add a user to the user DB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The UserService will create an event that the PostService consumes and adds the user to the post DB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access the PostService REST API and add a post for the user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call the PostService REST API and load the post and user from the post DB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call the UserService REST API and rename the user&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The UserService will create an event that the PostService consumes and updates the user’s name in the post DB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call the PostService REST API and load the post and renamed user from the post DB&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The user DB must be empty. You can delete the user.db (in the Visual Studio explorer) if you created users in previous steps of this guide. The calls to Database.EnsureCreated() will recreate the DBs on startup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Configure both projects to run as service&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ASQTbsd6i8oaxZvPAJm076w.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ASQTbsd6i8oaxZvPAJm076w.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change the App-URL of the PostService to another port&lt;/strong&gt; (e.g. &lt;a href="http://localhost:5001" rel="noopener noreferrer"&gt;http://localhost:5001&lt;/a&gt;) so that both projects can be run in parallel. Configure the solution to start both projects and start debugging:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AxxKmZRU52XKvpRUZ_0q-Gg.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AxxKmZRU52XKvpRUZ_0q-Gg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the Swagger UI to create a user in the UserService:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "name": "Chris",
 "mail": "chris@chris.com",
 "otherData": "Some other data"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The generated userId might be different in your environment:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Airm6bDO86SNAURShpWWT6w.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Airm6bDO86SNAURShpWWT6w.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The integration event replicates the user to the PostService:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AK8qzwMngXwwR6PzhnFYaTQ.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AK8qzwMngXwwR6PzhnFYaTQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can create a post in the PostServive Swagger UI (use your userId):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "title": "MyFirst Post",
  "content": "Some interesting text",
  "userId": 1
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Read all posts. The username is included in the result:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A5Jmh4yzAnARKeQQp96wMCA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A5Jmh4yzAnARKeQQp96wMCA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Change the username in the UserService Swagger UI:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "id": 1,
  "name": "My new name"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then read the posts again and see the changed 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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AxNOsX0lltHO-nG9LaEbHQA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AxNOsX0lltHO-nG9LaEbHQA.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Final Thoughts and Outlook
&lt;/h2&gt;

&lt;p&gt;You created the &lt;strong&gt;working basis for an event-driven microservice architecture&lt;/strong&gt;. Besides data replication, you can also use it for classic producer-consumer workflows, SAGAs, etc.&lt;/p&gt;

&lt;p&gt;Please make sure to &lt;strong&gt;adjust the code to use it in a production environment&lt;/strong&gt;: &lt;strong&gt;Clean up the code&lt;/strong&gt; and &lt;strong&gt;apply security best practices&lt;/strong&gt;. Apply .NET Core design patterns, error handling, etc. &lt;/p&gt;

&lt;p&gt;Currently messages could be lost in edge cases when RabbitMQ or the microservices crash. See my follow-up article on &lt;strong&gt;&lt;a href="https://dev.to/christianzink/the-outbox-pattern-in-event-driven-asp-net-core-microservice-architectures-89"&gt;how to apply the transactional outbox pattern and make the application more resilient&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;See my other articles on how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dev.to/christianzink/how-to-use-database-sharding-and-scale-an-asp-net-core-microservice-architecture-5h5l"&gt;Use Database Sharding and Scale your application&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dev.to/christianzink/how-to-build-an-asp-net-core-kubernetes-microservices-architecture-with-angular-on-local-docker-desktop-using-ingress-395n"&gt;Deploy your ASP.NET Core application to Kubernetes, use Angular for the UI&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/christianzink/databases-in-a-kubernetes-angular-net-core-microservice-architecture-22jc"&gt;Add MySql and MongoDB databases&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Please contact me if you have any questions, ideas, or suggestions.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Add MySql &amp; MongoDB to a Kubernetes .Net Core Microservice Architecture</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Tue, 04 May 2021 20:24:53 +0000</pubDate>
      <link>https://forem.com/christianzink/databases-in-a-kubernetes-angular-net-core-microservice-architecture-22jc</link>
      <guid>https://forem.com/christianzink/databases-in-a-kubernetes-angular-net-core-microservice-architecture-22jc</guid>
      <description>&lt;p&gt;How to add a MySQL DB and a MongoDB Replica Set in K8S on Docker Desktop using Persistent Volumes and Access the Databases from ASP.NET Core C# and Angular&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%2Fbv08svanq0lgj1z39aib.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%2Fbv08svanq0lgj1z39aib.png" alt="Alt Text"&gt;&lt;/a&gt; &lt;em&gt;Overview diagram of the components&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this guide, you will &lt;strong&gt;use databases&lt;/strong&gt; in a &lt;strong&gt;raw microservice-based cloud architecture&lt;/strong&gt;. It starts with a single &lt;strong&gt;MySQL&lt;/strong&gt; instance and continues to a &lt;strong&gt;MongoDB replica set&lt;/strong&gt; with a &lt;strong&gt;headless Kubernetes service&lt;/strong&gt;. Both databases use &lt;strong&gt;persistent volumes&lt;/strong&gt;. The databases are accessed by &lt;strong&gt;ASP.NET Core backend services&lt;/strong&gt; and use &lt;strong&gt;Angular as the frontend&lt;/strong&gt;. The application is exposed via an &lt;strong&gt;Ingress controller&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contents
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;MySQL in Kubernetes with Persistent Volume&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MongoDB Replica Set in Kubernetes as StatefulSet&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access the Databases in ASP.NET Core REST-API Services&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call the ASP.NET Core APIs from Angular&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploy the Angular App and the ASP.NET Core Services to Kubernetes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final Thoughts and Outlook&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. MySQL in Kubernetes with Persistent Volume
&lt;/h2&gt;

&lt;p&gt;You will deploy a single MySQL pod in Kubernetes on Docker Desktop. A service will make it accessible on port 3306. You will use a Kubernetes persistent volume so the MySQL data survives restarts or deletions of the pod.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MySQL Deployment in Kubernetes on Docker Desktop:&lt;/em&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa39g6yhqticr1qndx3si.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%2Fa39g6yhqticr1qndx3si.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Preparation
&lt;/h3&gt;

&lt;p&gt;Install &lt;a href="https://hub.docker.com/editions/community/docker-ce-desktop-windows" rel="noopener noreferrer"&gt;Docker Desktop for Windows&lt;/a&gt; and enable Kubernetes:&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%2Fcdn-images-1.medium.com%2Fmax%2F2744%2F1%2AIzjJfH56yoJGrxUQru1fSA.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%2Fcdn-images-1.medium.com%2Fmax%2F2744%2F1%2AIzjJfH56yoJGrxUQru1fSA.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create the Deployment
&lt;/h3&gt;

&lt;p&gt;Create the file mysql.yaml:&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Apply the yaml:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;kubectl apply -f mysql.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  (Optional) Use a Temporary MySQL Client Container to Access and Test the MySQL Deployment
&lt;/h3&gt;

&lt;p&gt;Create the container and connect to the MySQL database server (enter the command as one line):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;kubectl run -it — rm — image=mysql:5.6 — restart=Never mysql-client — mysql -h mysql -ppassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Create a new database:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt;create database testdb;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;You can delete the MySQL pod:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;kubectl delete pod -l app=mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Kubernetes will automatically recreate the pod. The persistent volume guarantees that the sql data survives the deletion and recreation of pods.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You will have to secure your MySQL configuration to use it in a production environment. Use Kubernetes secrets, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Further reading in the official Kubernetes documentation: &lt;a href="https://kubernetes.io/docs/tasks/run-application/run-single-instance-stateful-application/" rel="noopener noreferrer"&gt;Run a Single-Instance Stateful Application&lt;/a&gt; (MySQL)&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  2. MongoDB Replica Set in Kubernetes as StatefulSet
&lt;/h2&gt;

&lt;p&gt;You will use a StatefulSet to deploy two MongoDB pods to Kubernetes. Each pod uses its own persistent storage. Then you configure the two instances as a MongoDB replica set.&lt;/p&gt;

&lt;p&gt;A headless service makes the instances accessible on port 27017 and the deterministic names “mongod-0.mongodb-service” and “mongod-1.mongodb-service”:&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%2F6gsb51j6ru0vtptjopmi.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%2F6gsb51j6ru0vtptjopmi.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create the StatefulSet
&lt;/h3&gt;

&lt;p&gt;Create the file mongodb.yaml:&lt;/p&gt;


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



&lt;p&gt;Apply the yaml:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;kubectl apply -f mongodb.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Configure the MongoDB Replica Set
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;You will manually configure the replica set. See this article for how to uses a sidecar container for an automatic configuration: &lt;a href="https://kubernetes.io/blog/2017/01/running-mongodb-on-kubernetes-with-statefulsets/" rel="noopener noreferrer"&gt;Running MongoDB on Kubernetes with StatefulSets&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Connect to the mongod-0 pod:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;kubectl exec -it mongod-0 -c mongod-container bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Start the mongo client:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@mongod-0:/# mongo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Configure the replica set (enter the command as one line):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MainRepSet:PRIMARY&amp;gt; rs.initiate({ _id: “MainRepSet”, version: 1, members: [{ _id: 0, host: “mongod-0.mongodb-service.default.svc.cluster.local:27017” }, { _id: 1, host: “mongod-1.mongodb-service.default.svc.cluster.local:27017” } ]});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;You will have to secure your MongoDB configuration to use it in a production environment. Use Kubernetes secrets, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://deeptiman.medium.com/mongodb-statefulset-in-kubernetes-87c2f5974821" rel="noopener noreferrer"&gt;MongoDB StatefulSet in Kubernetes&lt;/a&gt; by &lt;a href="https://dev.toundefined"&gt;Deeptiman Pattnaik&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Access the Databases in ASP.NET Core REST-API Services
&lt;/h2&gt;

&lt;p&gt;You will create one ASP.NET Core web API to access the MySQL database and a second ASP.NET Core web API to connect to the MongoDB replica set. Finally, you will create a docker image for each API.&lt;/p&gt;

&lt;p&gt;This post focuses on database access. So I keep the other aspects short. See my previous post for details on &lt;a href="https://dev.to/christianzink/how-to-build-an-asp-net-core-kubernetes-microservices-architecture-with-angular-on-local-docker-desktop-using-ingress-395n"&gt;how to use K8S on Docker Desktop with Ingress to develop locally for the cloud using an ASP.NET Core REST API and Angular&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Install &lt;a href="https://visualstudio.microsoft.com/en/vs/community/" rel="noopener noreferrer"&gt;Visual Studio Community&lt;/a&gt; (it’s free) with the ASP.NET and web development workload.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create an API Using the MySQL Service
&lt;/h3&gt;

&lt;p&gt;Create an ASP.NET Core 5.0 Web API project and call it “MySqlApi”. Activate Docker and use the “Linux” setting. Disable HTTPS.&lt;/p&gt;

&lt;p&gt;Install the “MySqlConnector” NuGet package.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://mysqlconnector.net/tutorials/connect-to-mysql/" rel="noopener noreferrer"&gt;https://mysqlconnector.net/tutorials/connect-to-mysql/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Add a new controller “MySqlController”:&lt;/p&gt;


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



&lt;p&gt;The code will query and return all databases in the MySQL server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For easier understanding, all code is placed in one method. Please make sure to use proper configuration, etc. when running in a production environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Dockerize the API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the Visual Studio MySqlApi project rename the file “Dockerfile” in the Project-Explorer to “dockerfile” (first character lowercase). Then right-click the dockerfile and select “Create Docker Image”. This will also push the image to docker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create an API Using the MongoDB Service
&lt;/h3&gt;

&lt;p&gt;Create a second Web API project and call it “MongoDbApi”. Use the same settings as for the “MySqlApi” project.&lt;/p&gt;

&lt;p&gt;Install the “MongoDB.Driver” NuGet package.&lt;/p&gt;

&lt;p&gt;Add a new controller “MongoDbController”:&lt;/p&gt;


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


&lt;p&gt;The code will query and return all collections in the MongoDB replica set.&lt;/p&gt;

&lt;p&gt;Create the docker image as you did for the MySqlApi project.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Call the ASP.NET Core APIs from Angular
&lt;/h2&gt;

&lt;p&gt;You will create an Angular app to load and display the data from the ASP.NET APIs. Finally, you create a docker image that uses NGINX to serve the app.&lt;/p&gt;

&lt;p&gt;Download and install &lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js/NPM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Install Angular (I used Angular 9 when writing this guide):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;npm install -g @angular/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Create the app:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;ng new DemoApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Adjust the code in DemoApp\src\app&lt;/p&gt;

&lt;p&gt;Add the HttpClientModule to &lt;strong&gt;app.module.ts&lt;/strong&gt;:&lt;/p&gt;


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



&lt;p&gt;Call the REST-APIs in &lt;strong&gt;app.component.ts&lt;/strong&gt;:&lt;/p&gt;


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


&lt;p&gt;Display the loaded data in &lt;strong&gt;app.component.html&lt;/strong&gt;:&lt;/p&gt;


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


&lt;h3&gt;
  
  
  Dockerize the Angular App
&lt;/h3&gt;

&lt;p&gt;Create the file DemoApp\src\app\dockerfile&lt;/p&gt;

&lt;p&gt;Make sure the file “dockerfile” has no file extension. This will use NGINX to serve the app:&lt;/p&gt;


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


&lt;p&gt;Create the file “.dockerignore” (with a dot at the beginning):&lt;/p&gt;


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


&lt;p&gt;Open a command prompt and build the docker image (the dot at the end is important):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo\DemoApp&amp;gt;docker build -t demoapp .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  5. Deploy the Angular App and the ASP.NET Core Services to Kubernetes
&lt;/h2&gt;

&lt;p&gt;Install the NGINX Ingress controller (in one line on the command line):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.41.2/deploy/static/provider/cloud/deploy.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Create kubernetes.yaml:&lt;/p&gt;


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



&lt;p&gt;Apply the yaml:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;kubectl apply -f kubernetes.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;You are done!&lt;/strong&gt; Open your browser on http port 80 and see how your Kubernetes microservices are working:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AjLbLYA39hkO6oOT-pnKHKA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AjLbLYA39hkO6oOT-pnKHKA.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Final Thoughts and Outlook
&lt;/h2&gt;

&lt;p&gt;You created the &lt;strong&gt;working basis for a microservice-based cloud architecture&lt;/strong&gt; and &lt;strong&gt;used two different types of databases&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Please make sure to &lt;strong&gt;adjust the code to use it in a production environment&lt;/strong&gt;: &lt;strong&gt;Clean up the code&lt;/strong&gt; and &lt;strong&gt;apply security best practices&lt;/strong&gt;, use Kubernetes secrets and the latest secure images. Apply Angular and .NET Core design patterns, error handling, etc.&lt;/p&gt;

&lt;p&gt;See my follow-up posts &lt;strong&gt;&lt;a href="https://dev.to/christianzink/databases-in-a-kubernetes-angular-net-core-microservice-architecture-22jc"&gt;how to add MySql and MongoDB to your architecture&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://dev.to/christianzink/how-to-build-an-event-driven-asp-net-core-microservice-architecture-4fnh"&gt;how to use events for inter-microservice communication&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I will &lt;strong&gt;show you more in further posts&lt;/strong&gt;: Kubernetes secrets, security aspects like SSL, logging, debugging, CI/CD, Helm charts, (code) quality, (auto) scaling and self-healing, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See my other stories how to:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://levelup.gitconnected.com/how-to-sign-in-with-google-in-angular-and-use-jwt-based-net-core-api-authentication-rsa-6635719fb86c" rel="noopener noreferrer"&gt;Sign-In with Google in Angular and use JWT based .NET Core API Authentication with Asymmetrically (RSA) Signed Tokens&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://levelup.gitconnected.com/how-to-jwt-authenticate-with-angular-to-an-asp-net-4cfab5298d08" rel="noopener noreferrer"&gt;Use NSwag to Autogenerate a TypeScript Client to Access a .NET Core service API and use OpenAPI/Swagger to describe the API&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Please contact me if you have any questions, ideas, or suggestions.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>kubernetes</category>
      <category>microservices</category>
      <category>angular</category>
    </item>
    <item>
      <title>How to Build an ASP.NET Core C# Kubernetes Microservice Architecture with Angular on Local Docker Desktop using Ingress</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Sat, 10 Apr 2021 18:51:43 +0000</pubDate>
      <link>https://forem.com/christianzink/how-to-build-an-asp-net-core-kubernetes-microservices-architecture-with-angular-on-local-docker-desktop-using-ingress-395n</link>
      <guid>https://forem.com/christianzink/how-to-build-an-asp-net-core-kubernetes-microservices-architecture-with-angular-on-local-docker-desktop-using-ingress-395n</guid>
      <description>&lt;p&gt;In this guide, you will create a &lt;strong&gt;raw microservice-based cloud architecture&lt;/strong&gt;. It uses &lt;strong&gt;.NET Core with REST-APIs&lt;/strong&gt; in the backend services and &lt;strong&gt;Angular&lt;/strong&gt; as the frontend. All &lt;strong&gt;components are dockerized&lt;/strong&gt; and &lt;strong&gt;Kubernetes orchestrates&lt;/strong&gt; the containers. The application is &lt;strong&gt;exposed via an Ingress controller&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kubernetes &lt;strong&gt;runs in a local environment&lt;/strong&gt; with docker desktop. It is &lt;strong&gt;similar to a cloud environment&lt;/strong&gt;. You can &lt;strong&gt;use it for developing and testing&lt;/strong&gt;. There are &lt;strong&gt;no extra costs&lt;/strong&gt;. You can always &lt;strong&gt;easily deploy it later to the cloud&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Overview diagram of the components:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AHpXKDFXrMWduTglmXWDFBg.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AHpXKDFXrMWduTglmXWDFBg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Build the ASP.NET Core REST-API Backend&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create the Angular Frontend App&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dockerize the .NET Core API and the Angular App &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploy the Containers to Kubernetes on Docker Desktop&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final Thoughts and Outlook&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Build the ASP.NET Core REST-API Backend
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install &lt;a href="https://visualstudio.microsoft.com/en/vs/community/" rel="noopener noreferrer"&gt;Visual Studio Community&lt;/a&gt;&lt;/strong&gt; (it’s free) with the ASP.NET and web development workload. &lt;/p&gt;

&lt;p&gt;Create an ASP.NET Core 5.0 Web API project and call it “DemoApi”. Activate Docker and use the “Linux” setting. Disable HTTPS:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ACLDD1YGTeyM25sWWXuRxPg.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ACLDD1YGTeyM25sWWXuRxPg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a new controller “DemoController”. It just returns “Hello World”:&lt;/p&gt;


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


&lt;p&gt;Enable CORS for the Angular app on port 4200 (develop) and 80:&lt;/p&gt;


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


&lt;p&gt;Edit launchsettings.json and change the URL of the “DemoApi” configuration to Port 80. Then choose the DemoApi configuration and start it:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AbpZ2rJnEGx8SeRxyTehTHQ.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AbpZ2rJnEGx8SeRxyTehTHQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test it in the browser:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ADEKRomRgrH_guE1WjQ2JoA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ADEKRomRgrH_guE1WjQ2JoA.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Create the Angular Frontend App
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Download and install &lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js/NPM&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create the folder C:\dev\demo&lt;/strong&gt; and open a command prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install Angular&lt;/strong&gt; (I used Angular 9 when writing this guide):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;npm install -g @angular/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Create the app:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;ng new DemoApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://javascript.plainenglish.io/understanding-angular-and-creating-your-first-application-4b81b666f7b4" rel="noopener noreferrer"&gt;Understanding Angular and Creating Your First Application&lt;/a&gt; by gravity well (Rob Tomlin)&lt;/em&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Call the REST-API
&lt;/h3&gt;

&lt;p&gt;Adjust the code in C:\dev\demo\DemoApp\src\app&lt;/p&gt;

&lt;p&gt;Add the HttpClientModule to &lt;strong&gt;app.module.ts&lt;/strong&gt;:&lt;/p&gt;


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



&lt;p&gt;Call the REST-API in &lt;strong&gt;app.component.ts&lt;/strong&gt;:&lt;/p&gt;


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


&lt;p&gt;Display the loaded data in &lt;strong&gt;app.component.html&lt;/strong&gt;:&lt;/p&gt;


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


&lt;h3&gt;
  
  
  Test the app
&lt;/h3&gt;

&lt;p&gt;Make sure the .NET Core API is running. Start the Angular app and open it in your browser to see if everything is working:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo\DemoApp&amp;gt;ng serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;It should load and display the data “Hello World”:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AaDfnaJJr97GeWsJrGNCdNg.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AaDfnaJJr97GeWsJrGNCdNg.png"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Dockerize the .NET Core API and the Angular App
&lt;/h2&gt;

&lt;p&gt;I will show you two different ways to create and run a docker image. For the .NET Core API, you will use the UI. For the Angular App the command line.&lt;/p&gt;

&lt;p&gt;Install &lt;a href="https://hub.docker.com/editions/community/docker-ce-desktop-windows" rel="noopener noreferrer"&gt;Docker Desktop for Windows&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Dockerize the .NET Core API
&lt;/h3&gt;

&lt;p&gt;In the Visual Studio DemoApi project rename the file “Dockerfile” in the Project-Explorer to “dockerfile” (first character lowercase). Then right-click the dockerfile and select “Create Docker Image”.&lt;/p&gt;

&lt;p&gt;Check the build output if everything worked:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AEQiT3Ze5QPoX-YrfEFHR7Q.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AEQiT3Ze5QPoX-YrfEFHR7Q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stop your API project in Visual Studio if it is still running, because docker and Visual Studio can’t bind to port 80 at the same time.&lt;/p&gt;

&lt;p&gt;Run the “latest” “demoapi” image in the Docker Desktop UI and map port 80:&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%2Fcdn-images-1.medium.com%2Fmax%2F3116%2F1%2AW_q-lx_MU-M7foBOR51hYw.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%2Fcdn-images-1.medium.com%2Fmax%2F3116%2F1%2AW_q-lx_MU-M7foBOR51hYw.png"&gt;&lt;/a&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AuqUAphA6n5hIPvLQwAqi8g.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AuqUAphA6n5hIPvLQwAqi8g.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://faun.pub/how-to-deploy-a-net-5-api-in-a-kubernetes-cluster-53212af6a0e2" rel="noopener noreferrer"&gt;How to deploy a .NET 5 API in a Kubernetes cluster&lt;/a&gt; by Ivan Porta&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Dockerize the Angular App
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Create the file C:\dev\demo\DemoApp\dockerfile&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure the file “dockerfile” has no file extension&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This will use NGINX to serve the app:&lt;/p&gt;


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



&lt;p&gt;&lt;strong&gt;Create the file “.dockerignore”&lt;/strong&gt; (with a dot at the beginning):&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Open a command prompt and build the docker image&lt;/strong&gt; (the dot at the end is important):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo\DemoApp&amp;gt;docker build -t demoapp .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Stop your Angular app&lt;/strong&gt; if it still running. Use the command line (the whole command in one line) to start the app in the container and &lt;strong&gt;publish it on port 4200&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo\DemoApp&amp;gt;docker run --publish 4200:80 --name demoappcontainer demoapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://wkrzywiec.medium.com/build-and-run-angular-application-in-a-docker-container-b65dbbc50be8#" rel="noopener noreferrer"&gt;Build and run Angular application in a Docker container&lt;/a&gt; by Wojciech Krzywiec&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Test Both Containers
&lt;/h3&gt;

&lt;p&gt;You can list your containers (your container IDs and timestamps will be different):&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%2Fcdn-images-1.medium.com%2Fmax%2F2350%2F1%2AR3iLOr2nwwZVOgMbZyBHLQ.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%2Fcdn-images-1.medium.com%2Fmax%2F2350%2F1%2AR3iLOr2nwwZVOgMbZyBHLQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start the app&lt;/strong&gt; in the browser:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AaDfnaJJr97GeWsJrGNCdNg.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AaDfnaJJr97GeWsJrGNCdNg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop both containers&lt;/strong&gt; so the ports are not blocked for the next step of this guide:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo\DemoApp&amp;gt;docker stop demoappcontainer
C:\dev\demo\DemoApp&amp;gt;docker stop demoapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Deploy to Kubernetes on Docker Desktop
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Enable Kubernetes in Docker Desktop:&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%2Fcdn-images-1.medium.com%2Fmax%2F2744%2F1%2AIzjJfH56yoJGrxUQru1fSA.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%2Fcdn-images-1.medium.com%2Fmax%2F2744%2F1%2AIzjJfH56yoJGrxUQru1fSA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the NGINX Ingress controller&lt;/strong&gt; (in one line on the command line):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.41.2/deploy/static/provider/cloud/deploy.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;em&gt;See the &lt;a href="https://kubernetes.github.io/ingress-nginx/deploy/" rel="noopener noreferrer"&gt;NGINX Ingress Installation Guide&lt;/a&gt; for details&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create C:\dev\demo\kubernetes.yaml:&lt;/strong&gt;&lt;/p&gt;


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



&lt;p&gt;&lt;strong&gt;Apply the yaml:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev\demo&amp;gt;kubectl apply -f kubernetes.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will create two Kubernetes “Deployments” and for each deployment a “Service”. Both services are then exposed via an Ingress on port 80.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;See the &lt;a href="https://kubernetes.io/docs/home/" rel="noopener noreferrer"&gt;Kubernetes documentation&lt;/a&gt; for details&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first deployment uses the DemoAPI image and two replicas to show how it is possible to scale. The second deployment uses a single replica of the DemoApp. The Ingress routes to the service based on the path prefix:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AE5XwWdOIlvk2tm50Kmyf7Q.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AE5XwWdOIlvk2tm50Kmyf7Q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the Deployments
&lt;/h3&gt;

&lt;p&gt;“get deployments” shows the deployments and number of pods:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A60srpHN1Fee5vqkbgGVhUw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A60srpHN1Fee5vqkbgGVhUw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open your browser on port 80(!) and test your Kubernetes deployments:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AwqEW7Mj7LtSvuG9ZDWq8UA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AwqEW7Mj7LtSvuG9ZDWq8UA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://faun.pub/how-to-deploy-a-net-5-api-in-a-kubernetes-cluster-53212af6a0e2" rel="noopener noreferrer"&gt;How to deploy a .NET 5 API in a Kubernetes cluster&lt;/a&gt; by Ivan Porta&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Final Thoughts and Outlook
&lt;/h2&gt;

&lt;p&gt;You created the &lt;strong&gt;working basis for a microservice-based cloud architecture&lt;/strong&gt;. But &lt;strong&gt;there is still a lot missing&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;See my follow-up posts &lt;a href="https://dev.to/christianzink/databases-in-a-kubernetes-angular-net-core-microservice-architecture-22jc"&gt;how to add a MySql database and MongoDB replica set to your architecture&lt;/a&gt; and &lt;a href="https://dev.to/christianzink/how-to-build-an-event-driven-asp-net-core-microservice-architecture-4fnh"&gt;how to use events for inter-microservice communication&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will &lt;strong&gt;show you more in further posts&lt;/strong&gt;: Kubernetes secrets, security aspects like SSL, communication, logging, debugging, CI/CD, Helm charts, (code) quality, (auto) scaling and self-healing, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See my other stories how to:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/christianzink/how-to-sign-in-with-google-in-angular-and-use-jwt-based-net-core-api-authentication-rsa-1anl"&gt;Sign-In with Google in Angular and use JWT based .NET Core API Authentication with Asymmetrically (RSA) Signed Tokens&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/christianzink/how-to-jwt-authenticate-with-angular-to-an-asp-net-core-api-openapi-swagger-using-nswag-typescript-54fl"&gt;Use NSwag to Autogenerate a TypeScript Client to Access a .NET Core service API and use OpenAPI/Swagger to describe the API&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Please contact me if you have any questions, ideas, or suggestions.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>dotnet</category>
      <category>angular</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to JWT Authenticate with Angular to an ASP.NET Core C# API (OpenAPI/Swagger) using NSwag TypeScript</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Tue, 30 Mar 2021 14:15:32 +0000</pubDate>
      <link>https://forem.com/christianzink/how-to-jwt-authenticate-with-angular-to-an-asp-net-core-api-openapi-swagger-using-nswag-typescript-54fl</link>
      <guid>https://forem.com/christianzink/how-to-jwt-authenticate-with-angular-to-an-asp-net-core-api-openapi-swagger-using-nswag-typescript-54fl</guid>
      <description>&lt;p&gt;This guide shows you how to use &lt;strong&gt;&lt;a href="https://github.com/RicoSuter/NSwag" rel="noopener noreferrer"&gt;NSwag&lt;/a&gt; to automatically add an OpenAPI specification&lt;/strong&gt; to an &lt;strong&gt;ASP.NET Core REST-API&lt;/strong&gt;. It also serves the &lt;strong&gt;Swagger UI&lt;/strong&gt; to the browser. You will use &lt;strong&gt;NSwag Studio to generate a TypeScript client&lt;/strong&gt; and add the &lt;strong&gt;strongly typed client&lt;/strong&gt; to your &lt;strong&gt;Angular app&lt;/strong&gt;. Finally you &lt;strong&gt;authenticate&lt;/strong&gt; to the backend via a &lt;strong&gt;JWT bearer token&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Workflow and involved components:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AoNZerE7P3U9l4gWgq7gb4w.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AoNZerE7P3U9l4gWgq7gb4w.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background and Structure of this Guide
&lt;/h2&gt;

&lt;p&gt;When I wanted to create this kind of workflow I found descriptions showing some part of it. &lt;strong&gt;This guide brings the parts together&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This guide focuses on the key aspects&lt;/strong&gt;. I link to other articles for details and download locations of the tools. I use &lt;a href="https://visualstudio.microsoft.com/en/vs/community/" rel="noopener noreferrer"&gt;Visual Studio Community&lt;/a&gt; for ASP.NET Core 3.1 development.&lt;/p&gt;

&lt;p&gt;The guide uses the &lt;strong&gt;environment from my previous article&lt;/strong&gt; &lt;a href="https://dev.to/christianzink/how-to-sign-in-with-google-in-angular-and-use-jwt-based-net-core-api-authentication-rsa-1anl"&gt;“How to Sign-In with Google in Angular and use JWT based .NET Core API Authentication (RSA)”&lt;/a&gt;. You can &lt;strong&gt;easily apply it to your own Angular project&lt;/strong&gt; with ASP.NET Core backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contents
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use NSwag in the ASP.NET Core Backend&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate the TypeScript Client with NSwag Studio&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authenticate and Access the API from Angular&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final Thoughts and Outlook&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Use NSwag in the ASP.NET Core Backend
&lt;/h2&gt;

&lt;p&gt;Use the Visual Studio package manager to &lt;strong&gt;install the NSwag middleware&lt;/strong&gt; for the API project:&lt;br&gt;
Install-Package NSwag.AspNetCore&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit Startup.cs&lt;/strong&gt; (lines 18, 34, 35) and &lt;strong&gt;configure NSwag&lt;/strong&gt; to create the Swagger UI and OpenAPI specification:&lt;/p&gt;


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


&lt;h3&gt;
  
  
  Test the Result in the Browser
&lt;/h3&gt;

&lt;p&gt;Start the API project and &lt;strong&gt;open the Swagger UI&lt;/strong&gt; in your browser. In my project this is &lt;strong&gt;&lt;a href="https://localhost:5002/swagger" rel="noopener noreferrer"&gt;https://localhost:5002/swagger&lt;/a&gt;&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AxQDI2YrhF4tQ5n3uc3M1cw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AxQDI2YrhF4tQ5n3uc3M1cw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My API is a bit boring. Since it is only used to test the authentication, it has no parameters and no return values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Load the OpenAPI Specification
&lt;/h3&gt;

&lt;p&gt;You can also &lt;strong&gt;access the OpenAPI specification&lt;/strong&gt; (you will need the URL in the next step for NSwag Studio):&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A___uUORng1A4PmZZIJle9Q.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A___uUORng1A4PmZZIJle9Q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Further reading for this step and the next step of this guide: &lt;a href="https://elanderson.net/2019/12/using-nswag-to-generate-angular-client-for-an-asp-net-core-3-api/" rel="noopener noreferrer"&gt;Using NSwag to Generate Angular Client for an ASP.NET Core 3 API&lt;/a&gt; by Eric Anderson&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Generate the TypeScript Client with NSwag Studio
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rsuter.com/Projects/NSwagStudio/installer.php" rel="noopener noreferrer"&gt;&lt;strong&gt;Download NSwag Studio&lt;/strong&gt;&lt;/a&gt; and install it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start NSwag Studio&lt;/strong&gt; and &lt;strong&gt;enter the “Specification URL”&lt;/strong&gt; from the previous step of this guide (e.q. “&lt;a href="https://localhost:5002/swagger/v1/swagger.json" rel="noopener noreferrer"&gt;https://localhost:5002/swagger/v1/swagger.json&lt;/a&gt;”). &lt;strong&gt;Select “TypeScript Client” as “Outputs”&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%2Fcdn-images-1.medium.com%2Fmax%2F2710%2F1%2AgBIqDTkBrCLznTSeuh8Q2w.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%2Fcdn-images-1.medium.com%2Fmax%2F2710%2F1%2AgBIqDTkBrCLznTSeuh8Q2w.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure your API project from the previous step is running. NSwag Studio needs to access the OpenAPI sepcification.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Select the “Angular” template&lt;/strong&gt; and &lt;strong&gt;add “ApiBase” as “Base Class Name”&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AORj4Z0Qfz6jeREnEVJB1bg.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AORj4Z0Qfz6jeREnEVJB1bg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Select “transformOptions”&lt;/strong&gt; so later you can add the authentication header in the base class:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Ah3YvyO5FNpxFAXnvZ1oP5g.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Ah3YvyO5FNpxFAXnvZ1oP5g.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create the file apibase.ts&lt;/strong&gt; (e.g. at “C:\dev\apibase.ts”). The code sets the authentication header. ApiBase is the base class of the file that NSwag will generate for you:&lt;/p&gt;


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


&lt;p&gt;Then &lt;strong&gt;configure apibase.ts as extension code file&lt;/strong&gt; in NSwag Studio:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AHBhhJK2PO6rEcaAh3jvtSw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AHBhhJK2PO6rEcaAh3jvtSw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit the &lt;strong&gt;“Generate Outputs”&lt;/strong&gt; button and &lt;strong&gt;copy the output&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A6p9z9mKPt-UJWjWRncO27Q.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A6p9z9mKPt-UJWjWRncO27Q.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Authenticate and Access the API from Angular
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Create the file api.ts&lt;/strong&gt; in the app folder and &lt;strong&gt;paste the output from the previous step&lt;/strong&gt; of this guide:&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;Use the generated code in app.component.ts to access the API&lt;/strong&gt;. Import the file (line 4) and call the API (line 24–26):&lt;/p&gt;


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


&lt;h3&gt;
  
  
  Start the app
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c:\dev&amp;gt;ng serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Login and use the debugger and logging in the chrome browser to verify that calling and authenticating at the backend is working:&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%2Fcdn-images-1.medium.com%2Fmax%2F2496%2F1%2AJVH0R22k0Xyui1AdkHqEPA.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%2Fcdn-images-1.medium.com%2Fmax%2F2496%2F1%2AJVH0R22k0Xyui1AdkHqEPA.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Final Thoughts and Outlook
&lt;/h2&gt;

&lt;p&gt;You used &lt;strong&gt;NSwag to automatically create the OpenAPI/Swagger specification&lt;/strong&gt; for your API. Then &lt;strong&gt;NSwag Studio created the TypeScript client&lt;/strong&gt; and you used it in Angular.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The app is fully working!&lt;/strong&gt; But it is only a sample application and you will have to &lt;strong&gt;clean up the code&lt;/strong&gt;, use Angular and .NET Core design patterns, error handling, etc. to use it in production. &lt;/p&gt;

&lt;p&gt;You need to handle logging off and timeouts. Maybe you want API paths that don’t require authentication, etc. You can &lt;strong&gt;use the NSwag command line tool to automate the build pipeline&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;See also my &lt;strong&gt;follow-up story&lt;/strong&gt;: &lt;a href="https://dev.to/christianzink/how-to-build-an-asp-net-core-kubernetes-microservices-architecture-with-angular-on-local-docker-desktop-using-ingress-395n"&gt;How to deploy the .NET Core API and Angular App as Microservices to Kubernetes using Ingress and develop local using Docker Desktop&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please contact me if you have any questions, ideas, or suggestions.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>openapi</category>
      <category>angular</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>How to Sign-In with Google in Angular and use JWT based ASP.NET Core C# API Authentication (RSA)</title>
      <dc:creator>Christian Zink</dc:creator>
      <pubDate>Wed, 24 Mar 2021 20:33:55 +0000</pubDate>
      <link>https://forem.com/christianzink/how-to-sign-in-with-google-in-angular-and-use-jwt-based-net-core-api-authentication-rsa-1anl</link>
      <guid>https://forem.com/christianzink/how-to-sign-in-with-google-in-angular-and-use-jwt-based-net-core-api-authentication-rsa-1anl</guid>
      <description>&lt;p&gt;This guide shows you all the steps to build an &lt;strong&gt;Angular SPA&lt;/strong&gt; with a &lt;strong&gt;focus on authentication&lt;/strong&gt;. The single-page web application uses &lt;strong&gt;Sign-In with google&lt;/strong&gt; and &lt;strong&gt;angularx-social-login&lt;/strong&gt;. The &lt;strong&gt;.NET Core&lt;/strong&gt; authentication backend creates &lt;strong&gt;asymmetrically signed tokens&lt;/strong&gt; to access another &lt;strong&gt;REST-API&lt;/strong&gt;. The sample is &lt;strong&gt;fully working&lt;/strong&gt; and can be the basis for a &lt;strong&gt;microservice-like&lt;/strong&gt; architecture.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Authentication workflow and involved components:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Aab78gWYWjmvaG_E-BdFZAA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Aab78gWYWjmvaG_E-BdFZAA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background and Structure of this Guide
&lt;/h2&gt;

&lt;p&gt;When I wanted to create this kind of application I found many descriptions showing some part of it. &lt;strong&gt;This guide brings the parts together.&lt;/strong&gt; It is structured so you can optionally test every step before continuing to the next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This guide focuses on the key aspects&lt;/strong&gt;. I link to other articles for details and download locations of the tools. I use &lt;a href="https://visualstudio.microsoft.com/en/vs/community/" rel="noopener noreferrer"&gt;Visual Studio Community&lt;/a&gt; for ASP.NET Core 3.1 development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contents
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create the Angular App&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the Login Functionality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authentication API in the .NET Core Backend&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the Authentication API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the Access Token to Call the Service API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final Thoughts and Outlook&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Create the Angular App
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Download and install &lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js/NPM&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create the folder &lt;code&gt;C:\dev&lt;/code&gt; and open a command prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install Angular&lt;/strong&gt; (I used Angular 9 when writing this guide):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;npm install -g @angular/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Create the app:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;ng new SampleAuthentication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Install the angularx social login module&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;npm install angularx-social-login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  (Optional) Test the app
&lt;/h3&gt;

&lt;p&gt;Start the app and open it in your browser to see if everything is working:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dev&amp;gt;ng serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://javascript.plainenglish.io/understanding-angular-and-creating-your-first-application-4b81b666f7b4" rel="noopener noreferrer"&gt;Understanding Angular and Creating Your First Application&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Add the Login Functionality
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Get a client ID from google
&lt;/h3&gt;

&lt;p&gt;Create a configuration for your app in the &lt;a href="https://console.developers.google.com/" rel="noopener noreferrer"&gt;google developer console&lt;/a&gt;. And add the URL of the Angular debug server (&lt;a href="http://localhost:4200" rel="noopener noreferrer"&gt;http://localhost:4200&lt;/a&gt;) as “Authorised JavaScript origin”:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AbFGvfQSHNKUuRn7dV54bWA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AbFGvfQSHNKUuRn7dV54bWA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get the app’s Client ID&lt;/strong&gt; that google created for you. Copy the client ID. You will need it in further steps of this guide.&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AsI38TkCWmLHqdU6uTbaiNQ.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AsI38TkCWmLHqdU6uTbaiNQ.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Configure the social login module
&lt;/h3&gt;

&lt;p&gt;Use your google client ID and activate the social login module in the providers section of the app.module.ts:&lt;/p&gt;


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



&lt;p&gt;The code above also adds the HttpClient module, which will be needed later in this guide.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://medium.com/@danilrabizo/google-authentication-in-the-angular-application-e86df69be58a" rel="noopener noreferrer"&gt;Google authentication in Angular&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the Login Code
&lt;/h3&gt;

&lt;p&gt;Edit app.component.ts&lt;/p&gt;


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


&lt;p&gt;Edit app.component.html&lt;/p&gt;


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


&lt;h3&gt;
  
  
  (Optional) Test the login
&lt;/h3&gt;

&lt;p&gt;Start the app&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c:\dev&amp;gt;ng serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open the app URL in your browser, click “Sign in with google” to open the login dialogue. &lt;strong&gt;Sign in with your google credentials&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Am3im9xKxiLzwBfZVtvylvw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Am3im9xKxiLzwBfZVtvylvw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You are now logged in:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AmRr-WNh6KJ5ftHmV41-A0A.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AmRr-WNh6KJ5ftHmV41-A0A.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the debugger of the &lt;a href="https://www.google.com/intl/de_de/chrome/" rel="noopener noreferrer"&gt;chrome browser&lt;/a&gt; to get the idToken&lt;/strong&gt; and other information:&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%2Fcdn-images-1.medium.com%2Fmax%2F2252%2F1%2A9AuoqTfjLmbZzRwxxPpVnA.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%2Fcdn-images-1.medium.com%2Fmax%2F2252%2F1%2A9AuoqTfjLmbZzRwxxPpVnA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google creates a new idToken on every login. The token is only valid for a short time.&lt;/p&gt;

&lt;p&gt;Copy the idToken. You will need it in the next step of this guide if you want to manually test the API.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Authentication API in the .NET Core Backend
&lt;/h2&gt;

&lt;p&gt;The authentication API will use the idToken from google and verify it. Then it creates an access token that grants access to the other APIs of your app.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You will have to increase security before you run this code in a production environment: Shorter token lifetime and refreshs, maybe use sessions instead of tokens, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Install &lt;a href="https://visualstudio.microsoft.com/en/vs/community/" rel="noopener noreferrer"&gt;Visual Studio Community&lt;/a&gt;&lt;/strong&gt; (it’s free) with the ASP.NET and web development workload. Create an ASP.NET Core Web API project.&lt;/p&gt;
&lt;h3&gt;
  
  
  Verification of the idToken
&lt;/h3&gt;

&lt;p&gt;Use the Visual Studio package manager to &lt;strong&gt;install the google Auth Package&lt;/strong&gt;:&lt;br&gt;
&lt;code&gt;Install-Package Google.Apis.Auth&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create an API to verify the idToken&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change the value in &lt;code&gt;settings.Audience&lt;/code&gt; to your google client ID&lt;/strong&gt;, that you created in the first step of this guide:&lt;/p&gt;


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



&lt;p&gt;The call to &lt;code&gt;GoogleJsonWebSignature.ValidateAsync&lt;/code&gt; will throw in case of an error. The &lt;code&gt;Authenticate&lt;/code&gt; method uses the &lt;code&gt;JwtGeneratorclass&lt;/code&gt; to create and return a custom access token.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Custom Access Tokens in .NET Core Using Asymmetric Signatures
&lt;/h3&gt;

&lt;p&gt;With asymmetric JWT signing, only the authentication service knows the private key. The authentication service uses the private key to sign the access token and other APIs use only the public key to verify the access token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add the &lt;code&gt;JwtGenerator&lt;/code&gt; class&lt;/strong&gt;, that generates the token:&lt;/p&gt;


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


&lt;p&gt;The JwtGenerator uses the private key, that it gets passed from the UserController. &lt;strong&gt;Add the key to appsettings.json&lt;/strong&gt;, where the UserControler reads it from.&lt;/p&gt;


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


&lt;p&gt;You can use my private key for testing but you will have to create your own private-public key pair for production use. You will also have to adjust token lifetime, etc. for production use.&lt;/p&gt;

&lt;p&gt;*Further reading: &lt;a href="https://edstefanescu.medium.com/jwt-authentication-with-asymmetric-encryption-using-certificates-in-asp-net-core-7790d3ce499" rel="noopener noreferrer"&gt;JWT Authentication with Asymmetric Encryption using certificates in ASP.NET Core&lt;/a&gt;. *This story also shows how to create your own public-private key pair.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit Startup.cs&lt;/strong&gt;, which configures the ASP.NET Core backend to use the config, allow access from the Angular client, etc.:&lt;/p&gt;


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





&lt;h2&gt;
  
  
  4. Use the Authentication API
&lt;/h2&gt;

&lt;p&gt;Configure the .NET Core API project in debug mode on HTTPS port 5001:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Ab8RHjI1538Ph71TCZnPNdw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Ab8RHjI1538Ph71TCZnPNdw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Start the authentication API in Visual Studio:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AxJn_cUjtOKamk61z2UJ0sA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AxJn_cUjtOKamk61z2UJ0sA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  (Optional) Manually access the authentication API
&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://www.postman.com/downloads/" rel="noopener noreferrer"&gt;postman&lt;/a&gt; to access the API at: &lt;code&gt;https://localhost:5001/user/authenticate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Post the idToken, that you copied from the Chrome debugger in the second step of this guide:&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%2Fcdn-images-1.medium.com%2Fmax%2F2768%2F1%2AT_2Eh_o78-c9lDZ2tbeFtQ.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%2Fcdn-images-1.medium.com%2Fmax%2F2768%2F1%2AT_2Eh_o78-c9lDZ2tbeFtQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Call the authentication API from the app
&lt;/h3&gt;

&lt;p&gt;Use the Angular HttpClient to post the idToken to the authentication API. &lt;strong&gt;Modify app.component.ts&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Line 5: Import the HttpClient&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lines 21: Send the idToken&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


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


&lt;h3&gt;
  
  
  (Optional) Test the workflow in the app
&lt;/h3&gt;

&lt;p&gt;Do the same as in the first step of this guide.&lt;/p&gt;

&lt;p&gt;Start the app&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c:\dev&amp;gt;ng serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Login and use the debugger and logging in the chrome browser to verify that calling the backend and receiving the generated access token is working:&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%2Fcdn-images-1.medium.com%2Fmax%2F2330%2F1%2AGfKqVFpDML6BbdOyQ2VyCg.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%2Fcdn-images-1.medium.com%2Fmax%2F2330%2F1%2AGfKqVFpDML6BbdOyQ2VyCg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the access token if you want to do a manual test in the next step of this guide.&lt;/p&gt;


&lt;h2&gt;
  
  
  5. Use the Access Token to Call the Service API
&lt;/h2&gt;

&lt;p&gt;The access token from the authentication API will grant access to this new service API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create an ASP.NET Core Web API project&lt;/strong&gt; and configure it in debug mode on HTTPS port 5002:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ATS8EN5S9_bjIrruOav8-tw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ATS8EN5S9_bjIrruOav8-tw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a controller:&lt;/strong&gt;&lt;/p&gt;


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



&lt;p&gt;&lt;strong&gt;Edit Startup.cs&lt;/strong&gt;. It took me a while to configure the validation of the token. The public key to the corresponding private key of the authentication API is used to validate the token. The hard part was to inject the public key. See the comments in the source code for details:&lt;/p&gt;


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


&lt;h3&gt;
  
  
  (Optional) Use postman to access the service API
&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://www.postman.com/downloads/" rel="noopener noreferrer"&gt;postman&lt;/a&gt; to access the service API via HTTP GET at: &lt;code&gt;https://localhost:5002/secured&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the previous part of this guide, you copied the access token in the Chrome debugger. &lt;strong&gt;Add it as bearer token to the call&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%2Fcdn-images-1.medium.com%2Fmax%2F2090%2F1%2AVbGeA5MIS2ou-zttKw_jPQ.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%2Fcdn-images-1.medium.com%2Fmax%2F2090%2F1%2AVbGeA5MIS2ou-zttKw_jPQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Final step of this guide: Use the app to access the service API
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Modify app.component.ts&lt;/strong&gt; to post the access key to the service API:&lt;/p&gt;


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


&lt;p&gt;&lt;strong&gt;You successfully accessed the protected service API:&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AuX4I0zm1oHsm1ea-w10ufA.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AuX4I0zm1oHsm1ea-w10ufA.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Final Thoughts and Outlook
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your app is now fully working!&lt;/strong&gt; But it is only a sample application and you will have to &lt;strong&gt;clean up the code&lt;/strong&gt; and &lt;strong&gt;apply security best practices&lt;/strong&gt;, maybe use a more secure OAuth flow, use Angular and .NET Core design patterns, error handling, etc. to use it in production. And you need your own private-public key pair (&lt;a href="https://edstefanescu.medium.com/jwt-authentication-with-asymmetric-encryption-using-certificates-in-asp-net-core-7790d3ce499" rel="noopener noreferrer"&gt;see JWT Authentication with Asymmetric Encryption using certificates in ASP.NET Core&lt;/a&gt; by &lt;br&gt;
Eduard Stefanescu).&lt;/p&gt;

&lt;p&gt;You can &lt;strong&gt;add other providers&lt;/strong&gt; like facebook or your own account database. &lt;br&gt;
See also my &lt;strong&gt;follow-up stories&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/christianzink/how-to-jwt-authenticate-with-angular-to-an-asp-net-core-api-openapi-swagger-using-nswag-typescript-54fl"&gt;How to use NSwag to autogenerate the TypeScript client&lt;/a&gt; to access the .NET Core service API and use OpenAPI/Swagger to describe the API&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/christianzink/how-to-build-an-asp-net-core-kubernetes-microservices-architecture-with-angular-on-local-docker-desktop-using-ingress-395n"&gt;How to deploy the .NET Core API and Angular App as Microservices to Kubernetes using Ingress and develop local using Docker Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Please contact me if you have any questions, ideas, or suggestions.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>dotnet</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
