<?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>DEV Community: Egor Pavlikhin</title>
    <description>The latest articles on DEV Community by Egor Pavlikhin (@egorpavlikhin).</description>
    <link>https://dev.to/egorpavlikhin</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%2F334959%2F0efd3004-cf07-4941-bbc0-8ff44dbeced5.jpg</url>
      <title>DEV Community: Egor Pavlikhin</title>
      <link>https://dev.to/egorpavlikhin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/egorpavlikhin"/>
    <language>en</language>
    <item>
      <title>Dockerize Angular 9-10 App With Cached Dependencies</title>
      <dc:creator>Egor Pavlikhin</dc:creator>
      <pubDate>Fri, 14 Aug 2020 02:12:47 +0000</pubDate>
      <link>https://dev.to/egorpavlikhin/dockerize-angular-9-10-app-with-cached-dependencies-31lp</link>
      <guid>https://dev.to/egorpavlikhin/dockerize-angular-9-10-app-with-cached-dependencies-31lp</guid>
      <description>&lt;p&gt;I've had a perfectly fine Angular app built into a Docker container image, which became terribly slow to build after upgrading to Angular 9, with build process recompiling dependencies with ngcc every single build. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Compiling @angular/core : fesm5 as esm5&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here are the steps that you need to take to make your builds fast again.&lt;/p&gt;

&lt;p&gt;1) Add .dockerignore file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.git
.gitignorenpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;2) Add default.conf (this is for your nginx server)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
  listen 80;

  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files $uri$args $uri$args/ $uri $uri/ /index.html =404;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;3) Add Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### STAGE 1: Build ###
FROM node:14.8.0-alpine AS build

WORKDIR /usr/src/app
ENV PATH=${PATH}:./node_modules/.bin
ENV NODE_PATH=/usr/src/app/node_modules
ADD package.json ./
ADD package-lock.json ./
RUN npm ci
RUN ngcc
ADD . .
RUN ng build --prod

### STAGE 2: Run ###
FROM nginx:1.17.1-alpine
COPY --from=build /usr/src/app/dist/web /usr/share/nginx/html
COPY default.conf /etc/nginx/conf.d/default.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;4) Profit. Run &lt;code&gt;docker build .&lt;/code&gt; and enjoy. NGCC will cache built dependencies now and subsequent builds will be as fast as usual.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>docker</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>6 "Things-I-Wish-I-Knew" After 15 Years As A Programmer</title>
      <dc:creator>Egor Pavlikhin</dc:creator>
      <pubDate>Fri, 31 Jul 2020 16:04:27 +0000</pubDate>
      <link>https://dev.to/egorpavlikhin/6-things-i-wish-i-knew-after-15-years-as-a-programmer-3gc2</link>
      <guid>https://dev.to/egorpavlikhin/6-things-i-wish-i-knew-after-15-years-as-a-programmer-3gc2</guid>
      <description>&lt;h2&gt;
  
  
  1. Fundamentals are key
&lt;/h2&gt;

&lt;p&gt;Everyone loves to try new and fancy technology and just start hacking. Even today I am still guilty of this. However, it's been proven over and over that learning fundamentals is far more productive. This applies both to general programming concepts and specific frameworks and libraries. If you understand how something works conceptually not only can you deduce how 80% of the thing works, but also fix issues far faster than desperately searching StackOverflow.&lt;/p&gt;

&lt;p&gt;The thing about learning fundamentals is that it is ultimately boring. Often you have nothing to show for it for the longest time, and some concepts will be completely alien and useless to you. The important thing is to know what you don't know. All you need to do is remember where to go back to for information once you really need to. You are severely limiting yourself if you "don't know what you don't know".&lt;/p&gt;

&lt;p&gt;Once you hit that "oh I know the perfect solution for this, I've read about it here and here!" there will be no further need to prove that fundamentals are key.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Know your own and your code's limits
&lt;/h2&gt;

&lt;p&gt;Perfect code doesn't exist, just like perfect software doesn't exist. Software is never finished and neither is code. Therefore it's imperative to understand why your code is imperfect, how it could be better, and, most importantly, what the limits are. What can your code do really well and when does it perform poorly?&lt;/p&gt;

&lt;p&gt;It is hard to predict how your code is going to be used in the future, but if you analyzed the corner cases it will be much easier to estimate how difficult future changes are going to be.&lt;/p&gt;

&lt;p&gt;On the other hand don't be afraid to admin defeat when you don't know something well enough to implement in a reasonable time frame. When I was just starting out I tried building an interactive HTML tree for constructing a multi level menu of a website (that was before jQuery was a thing and IE had 90% browser share). It took me a week just to build the basic tree with drag-and-drop and one more week to realize the whole thing is way too brittle and will take months to bug fix. It would have save 2 weeks of my life to just admit that the task is too difficult and a simpler solution (such as limiting tree depth to 2 or 3) would be more appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Mature, respected libraries have bugs
&lt;/h2&gt;

&lt;p&gt;Just recently I have encountered a bug in .Net tooling for web services that has existed for half a decade and is likely never going to be fixed. There are bugs in MySQL that still have not been fixed after more than a decade. Some bugs exist for &lt;a href="https://www.gizmodo.com.au/2008/05/twenty_five_year_old_unix_bug_finally_fixed-2/"&gt;25 years before getting fixed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These bugs are the most frustrating of all - you never suspect a mature library, you suspect your own code. These occurrences are not as frequent these days as 10 years ago, but they still happen and you should be ready for them. Sometimes the library doesn't work and you need to re-implement the same thing yourself (ideally make a pull request and fix the bug in the source).&lt;/p&gt;

&lt;p&gt;Funnily enough, StackOverflow is littered with answers that recommend a library, when the original question originated because of bugs in said library. I have at least &lt;a href="https://stackoverflow.com/questions/2557247/easiest-way-to-retrieve-cross-browser-xmlhttprequest"&gt;one such question&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Code quality is in the eyes of the beholder
&lt;/h2&gt;

&lt;p&gt;No code is perfect. Some is really good, but perfection is too subjective. There is no point in blaming yourself or, worse, your colleagues for poor quality, unless you can produce a measurably better solution. And no, just because something looks better does not mean that it's a better solution.&lt;/p&gt;

&lt;p&gt;Chasing perfection often leads to unnecessary abstractions that end up never being used by any other code, but the original use case it was designed for. Writing code that is easy to refactor later on is far more important. Follow a consistent style with your teammates, use abstractions already available in your framework of choice and stick to &lt;a href="https://en.wikipedia.org/wiki/SOLID"&gt;SOLID&lt;/a&gt; and in most cases that will be enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Complex subjects are easier than you think
&lt;/h2&gt;

&lt;p&gt;We all learn "divide and conquer" as a strategy for solving programming tasks. This strategy works equally well for learning new things. A new tech or idea looks overwhelmingly complex? Split it into smaller chunks of unknowns. Make a list of small things that you need to learn first, before getting to bigger concepts. Each point should take no more than a day to learn.&lt;/p&gt;

&lt;p&gt;These days it's a lot easier to do, with websites like &lt;a href="https://dev.toPluralsight"&gt;Pluralsight&lt;/a&gt;, where content is broken down into streams, which are broken down into courses, which are further broken down into modules and chapters.&lt;/p&gt;

&lt;p&gt;This might seem obvious - indeed educational content has been done like this since the beginning of time. However, it is too easy to overlook when you are educating yourself and not consuming a prepared course. Not every course or book is structured well, and they cannot know what your level of knowledge is, so be prepared to go on a search of your own - see point # 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Setup CI/CD first
&lt;/h2&gt;

&lt;p&gt;Automated build process saves everyone so much time it should always be the first step in setting up a project. Even if it saves you just 5 minutes a day over a year it's about 3 working days saved every year. Reality is that not only it saves you 5 minutes doing the same thing over and over, but it also eliminates a lot of human errors and inevitable deployment bugs.&lt;/p&gt;

&lt;p&gt;Automated deployment is even better, especially if you have multiple environments and automated verification process. &lt;a href="https://octopus.com/"&gt;OctopusDeploy&lt;/a&gt; is the best solution in this category. It's so good it should really be in its own category.&lt;/p&gt;

&lt;p&gt;And don't forget that everything should be automated, including database deployment. Here my favorite solution is &lt;a href="https://dbup.readthedocs.io/en/latest/"&gt;DbUp&lt;/a&gt;, it's quite specific to .Net world, but the general concept is amazing in its simplicity. I have tried several DB migration and change tracking tools over the years (&lt;a href="https://www.jetbrains.com/datagrip/"&gt;DataGrip&lt;/a&gt; is one of the best for developers), but DbUp really nails the concept as it creates easy to follow, repeatable process that is completely transparent to everyone - developers, IT, DBAs.&lt;/p&gt;

</description>
      <category>career</category>
      <category>beginners</category>
      <category>learning</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Validation of nested form inputs (within components) in Angular</title>
      <dc:creator>Egor Pavlikhin</dc:creator>
      <pubDate>Wed, 25 Mar 2020 01:01:58 +0000</pubDate>
      <link>https://dev.to/egorpavlikhin/validation-of-nested-form-inputs-within-components-in-angular-327h</link>
      <guid>https://dev.to/egorpavlikhin/validation-of-nested-form-inputs-within-components-in-angular-327h</guid>
      <description>&lt;p&gt;Mostly writing this for myself, so I don't forget. Google articles for this topic usually show huge walls of text for more complicated cases, but that's not what you'd normally need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; when you have input fields within a component template the parent form will ignore these fields for validation purposes. So, a structure like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;angularForm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ngForm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;nested&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/app-nested-inputs&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;angularForm.form.invalid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nx"&gt;nested&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xxx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The submit button will still be active, even when the form is invalid, unless you add the following to &lt;code&gt;nested-inputs.component.ts&lt;/code&gt; Component directive&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;viewProviders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ControlContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useExisting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NgForm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So the Component directive will looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nested-inputs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./nested-inputs.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./nested-inputs.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;viewProviders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ControlContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useExisting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NgForm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Just a little tip to save you a few hours of headache :)&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Map JSON object to Controller Action with multiple parameters</title>
      <dc:creator>Egor Pavlikhin</dc:creator>
      <pubDate>Tue, 10 Mar 2020 05:47:06 +0000</pubDate>
      <link>https://dev.to/egorpavlikhin/map-json-object-to-controller-action-with-multiple-parameters-4en8</link>
      <guid>https://dev.to/egorpavlikhin/map-json-object-to-controller-action-with-multiple-parameters-4en8</guid>
      <description>&lt;p&gt;Last week I was surprised to find out that .Net Core still doesn't support something that every JavaScript/TypeScript framework does: mapping JSON object to multiple action parameters just isn't supported. You are supposed to define a &lt;code&gt;Model&lt;/code&gt; class, add it as a single parameter and mark it with &lt;code&gt;[FromBody]&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This just didn't sit right with me. Why declare a whole new class for every single Action with just a few parameters. What do I need to do to have a controller method as simple as this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;SometMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I couldn't find an answer, so I wrote my own package to do that.&lt;br&gt;
You can &lt;a href="https://www.nuget.org/packages/JsonParametersModelBinder/"&gt;download it here&lt;/a&gt; or &lt;a href="https://github.com/egorpavlikhin/JsonParametersModelBinder"&gt;view source here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's very simple, and quite slow for now (as it re-parses the whole request for every parameter), but does the job for simple cases.&lt;/p&gt;
&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Given JSON&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;c&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt;&lt;br&gt;
Add attribute &lt;code&gt;JsonParameters&lt;/code&gt; to your action&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"two"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;JsonParameters&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;TwoParameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt;&lt;br&gt;
Enhoy simplified workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;Thanks to &lt;a href="https://github.com/tchivs"&gt;tchivs&lt;/a&gt; for providing &lt;a href="https://github.com/dotnet/runtime/issues/29690#issuecomment-571969037"&gt;code to map to dynamic types&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Postgres Arrays: JOIN table with matching ids</title>
      <dc:creator>Egor Pavlikhin</dc:creator>
      <pubDate>Thu, 13 Feb 2020 22:04:39 +0000</pubDate>
      <link>https://dev.to/egorpavlikhin/postgres-arrays-join-table-with-matching-ids-37bm</link>
      <guid>https://dev.to/egorpavlikhin/postgres-arrays-join-table-with-matching-ids-37bm</guid>
      <description>&lt;p&gt;Postgres has a wonderful feature that allows you to store any &lt;a href="https://www.postgresql.org/docs/9.1/arrays.html"&gt;data type as an array&lt;/a&gt;. You can expand this to represent a one-to-many relationship and use it like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;unnest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;410&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Be aware of potential issues when working with arrays though. While they are good if you want to store point-in-time data where you "write and forget", maintaining such data model can be troublesome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No foreign keys support for arrays&lt;/li&gt;
&lt;li&gt;Limited support in ORMs and libraries&lt;/li&gt;
&lt;li&gt;To update an array you need to re-write all of it&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>postgres</category>
      <category>database</category>
    </item>
    <item>
      <title>Temporal tables or JSON storage</title>
      <dc:creator>Egor Pavlikhin</dc:creator>
      <pubDate>Thu, 13 Feb 2020 04:00:55 +0000</pubDate>
      <link>https://dev.to/egorpavlikhin/temporal-tables-or-json-storage-3gbg</link>
      <guid>https://dev.to/egorpavlikhin/temporal-tables-or-json-storage-3gbg</guid>
      <description>&lt;h1&gt;
  
  
  Problem Definition
&lt;/h1&gt;

&lt;p&gt;Imagine you run a small business and have a simple e-commerce system with Products, Orders and Customers: a customer places an order with a bunch of products inside. You ship the order and mark is as &lt;em&gt;Done&lt;/em&gt;. The Customer then logs in and changes their shipping address. You go back to your owner's dashboard to look at past orders and see the new address as the shipment address, although it is clearly wrong.&lt;/p&gt;

&lt;p&gt;I want to look at just one possible solution to this problem (in context of relational databases, specifically Postgres), which is to store all data relative to the order somewhere close to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 1 - Add columns to Order table
&lt;/h2&gt;

&lt;p&gt;First thing we could try is to denormalize the data and store everything related to an order directly in the Order table. This is quite straight forward and easy to find tools to support it with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IyhtXlTX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/van2m03cckcmfowg0gor.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IyhtXlTX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/van2m03cckcmfowg0gor.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Naturally aligns with well supported table structures.&lt;/li&gt;
&lt;li&gt;Data is strongly typed and easily explorable - you always know what you will get back.&lt;/li&gt;
&lt;li&gt;Easy to query and analyze.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Drawbacks
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Hard to support changes to the way customer or product data is stored. Columns moved, deleted or added to the original tables might create problems in historic data.&lt;/li&gt;
&lt;li&gt;Similarly, if product is deleted it might be hard or impossible to derive analytical data about it.&lt;/li&gt;
&lt;li&gt;Only values are stored - schemas have to be versioned externally (this can be mitigated through Views).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Solution 2 - Temporal tables
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Temporal_database"&gt;Temporal tables&lt;/a&gt; are quite powerful, but unfortunately poorly supported in open-source database engines. There are extensions that exist for Postgres, but it's hard to say if they are production ready and there is definitely no official support.&lt;/p&gt;

&lt;p&gt;The idea is to have a historic table next to data tables (so Customers and Customers_History) with the data table holding the current version of the data and historic tables storing all the rows that have ever existed with a START (CREATED) and END (CHANGED) dates.&lt;/p&gt;

&lt;p&gt;It then becomes quite easy to link a row in the Orders table back to a specific Customer record in any point in time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WPhSNX7E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/r9z3ahl09m347olgcdkk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WPhSNX7E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/r9z3ahl09m347olgcdkk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Normalized data. Everything lives where you expect it to live.&lt;/li&gt;
&lt;li&gt;You can get a snapshot of the entire data set at any point in time or exclude certain periods, which can be very powerful for analytical purposes.&lt;/li&gt;
&lt;li&gt;Audit and historic data retention is the natural purpose of temporal tables.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Drawbacks
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Hard to support changes to the way customer or product data is stored. Columns moved, deleted or added to the original tables might create problems in historic data.&lt;/li&gt;
&lt;li&gt;Poor or no support in Postgres - you will have to support this manually and there is no perfect option.&lt;/li&gt;
&lt;li&gt;Querying data becomes more complex.&lt;/li&gt;
&lt;li&gt;Only values are stored as historical data - schemas have to be versioned externally (this can be mitigated through Views).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Solution 3 - JSON blobs
&lt;/h2&gt;

&lt;p&gt;At last we arrive at my favorite solution - storing snapshots of data as JSON. The possibilities are endless here, but essentially the proposition is to have one column per each data type (Customer, Address, Product) and store all related data in plain JSON object. JSON data can even be versioned or you can store JSON Schema next to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3ThnuCJx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4wugeo1cnurpkbk0g19h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3ThnuCJx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4wugeo1cnurpkbk0g19h.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Postgres has excellent support for JSON data in &lt;code&gt;jsonb&lt;/code&gt; type. It's fairly easy to query.&lt;/li&gt;
&lt;li&gt;Data can be versioned directly inside the columns.&lt;/li&gt;
&lt;li&gt;All relevant data is immediately available on the relevant row. If your front end is using JSON you can pass this data directly from DB with no processing.&lt;/li&gt;
&lt;li&gt;To simplify analytics virtual columns / views can be easily created.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Drawbacks
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Data in JSON blobs can be inconsistent. You will have to look after it yourself. Most likely you will need to store metadata about each version externally.&lt;/li&gt;
&lt;li&gt;Queries will be slower. Much slower. And somewhat harder.&lt;/li&gt;
&lt;li&gt;Analytics are harder and most out of the box solutions will likely not work.&lt;/li&gt;
&lt;li&gt;DB size will grow significantly, since data will be essentially duplicated for each order. And hopefully you have a lot more orders that products and customers.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;For me the clear winner is &lt;strong&gt;JSON&lt;/strong&gt; - my back-end is in NodeJS / NestJS and front-end is in Angular, so manipulating JSON data comes naturally, but I can see how the first option might be attractive to traditional applications.&lt;/p&gt;

&lt;p&gt;What would you choose?&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
