<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Thiago Passos]]></title><description><![CDATA[I'm Thiago Passos, a Solution Architect sharing what I've been working with and learning. Love technology, dancing and I get unfriendly when I'm hungry.]]></description><link>https://passos.com.au/</link><image><url>https://passos.com.au/favicon.png</url><title>Thiago Passos</title><link>https://passos.com.au/</link></image><generator>Ghost 1.10</generator><lastBuildDate>Sun, 08 Nov 2026 07:04:22 GMT</lastBuildDate><atom:link href="https://passos.com.au/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[BFF with YARP, Docker and .NET]]></title><description><![CDATA[<div class="kg-card-markdown"><p><strong>Backend for Frontend (BFF)</strong> is a pattern that creates a dedicated backend service for each frontend (e.g., web, mobile) to better optimize API responses, enhance security, and streamline communication between frontends and backend services. In this context, using <strong>YARP (Yet Another Reverse Proxy)</strong> and <strong>Docker</strong> can significantly improve routing,</p></div>]]></description><link>https://passos.com.au/backend-for-frontend-bff/</link><guid isPermaLink="false">66fb563fce001c2becd68716</guid><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Tue, 01 Oct 2024 01:56:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1608452964553-9b4d97b2752f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE0fHxiYWNrZW5kfGVufDB8fHx8MTcyNzc0NzcyM3ww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1608452964553-9b4d97b2752f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE0fHxiYWNrZW5kfGVufDB8fHx8MTcyNzc0NzcyM3ww&ixlib=rb-4.0.3&q=80&w=1080" alt="BFF with YARP, Docker and .NET"><p><strong>Backend for Frontend (BFF)</strong> is a pattern that creates a dedicated backend service for each frontend (e.g., web, mobile) to better optimize API responses, enhance security, and streamline communication between frontends and backend services. In this context, using <strong>YARP (Yet Another Reverse Proxy)</strong> and <strong>Docker</strong> can significantly improve routing, scalability, and service orchestration.</p>
<h3 id="benefitsofcombiningyarpanddockerinbffarchitecture"><strong>Benefits of Combining YARP and Docker in BFF Architecture</strong></h3>
<ol>
<li><strong>Optimized API for Frontends</strong>: Each frontend gets a tailored API with BFF, ensuring optimized data delivery, reduced over-fetching, and under-fetching.</li>
<li><strong>Separation of Concerns</strong>: BFF handles frontend-specific logic, while YARP manages request routing between multiple services.</li>
<li><strong>Scalability and Flexibility</strong>: Docker enables each BFF or backend service to be containerized, making scaling easier.</li>
<li><strong>Service Isolation and Deployment</strong>: Docker provides isolation of services, which can then be deployed consistently across various environments.</li>
<li><strong>Routing and Load Balancing</strong>: YARP simplifies routing and load balancing between backend services, managed centrally through the BFF.</li>
</ol>
<hr>
<h3 id="yarpforbff"><strong>YARP for BFF</strong></h3>
<p><strong>YARP</strong> is a customizable reverse proxy that helps in routing frontend requests to backend services. In a <strong>BFF architecture</strong>, YARP can route frontend requests to appropriate backend services while abstracting complex service interactions from the frontend.</p>
<h4 id="yarpconfigurationinbff"><strong>YARP Configuration in BFF</strong></h4>
<ol>
<li>
<p><strong>Install YARP NuGet package:</strong></p>
<pre><code class="language-bash">dotnet add package Microsoft.ReverseProxy --version 1.0.0
</code></pre>
</li>
<li>
<p><strong>Set up YARP routing in <code>appsettings.json</code>:</strong></p>
<pre><code class="language-json">{
  &quot;ReverseProxy&quot;: {
    &quot;Routes&quot;: {
      &quot;route1&quot;: {
        &quot;ClusterId&quot;: &quot;cluster1&quot;,
        &quot;Match&quot;: {
          &quot;Path&quot;: &quot;/api/service1/{**catch-all}&quot;
        }
      },
      &quot;route2&quot;: {
        &quot;ClusterId&quot;: &quot;cluster2&quot;,
        &quot;Match&quot;: {
          &quot;Path&quot;: &quot;/api/service2/{**catch-all}&quot;
        }
      }
    },
    &quot;Clusters&quot;: {
      &quot;cluster1&quot;: {
        &quot;Destinations&quot;: {
          &quot;destination1&quot;: {
            &quot;Address&quot;: &quot;https://backend-service1/api/&quot;
          }
        }
      },
      &quot;cluster2&quot;: {
        &quot;Destinations&quot;: {
          &quot;destination2&quot;: {
            &quot;Address&quot;: &quot;https://backend-service2/api/&quot;
          }
        }
      }
    }
  }
}
</code></pre>
</li>
<li>
<p><strong>Configure YARP in <code>Startup.cs</code>:</strong></p>
<pre><code class="language-csharp">public void ConfigureServices(IServiceCollection services)
{
    services.AddReverseProxy()
            .LoadFromConfig(Configuration.GetSection(&quot;ReverseProxy&quot;));
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseEndpoints(endpoints =&gt;
    {
        endpoints.MapReverseProxy();
    });
}
</code></pre>
</li>
</ol>
<p>In this setup, YARP efficiently routes requests based on the path patterns (e.g., <code>/api/service1/*</code> is routed to <code>backend-service1</code>, and <code>/api/service2/*</code> is routed to <code>backend-service2</code>).</p>
<hr>
<h3 id="dockerizingthebff"><strong>Dockerizing the BFF</strong></h3>
<p><strong>Docker</strong> provides a way to package the BFF and other backend services into isolated containers, making the deployment and orchestration of services more manageable and scalable.</p>
<h4 id="benefitsofdockerizingbffandbackendservices"><strong>Benefits of Dockerizing BFF and Backend Services:</strong></h4>
<ol>
<li><strong>Portability</strong>: Docker ensures that your services run consistently across different environments.</li>
<li><strong>Isolation</strong>: Each service (BFF, backend services) is isolated, ensuring that they don't interfere with each other.</li>
<li><strong>Scalability</strong>: Docker containers can be scaled independently using container orchestration platforms like <strong>Kubernetes</strong>.</li>
</ol>
<h4 id="dockerfileforbff"><strong>Dockerfile for BFF:</strong></h4>
<ol>
<li>
<p><strong>Create a <code>Dockerfile</code></strong>:</p>
<pre><code class="language-Dockerfile"># Use the official ASP.NET Core runtime as a base image
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80

# Use the SDK image for building the application
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY [&quot;BFF/BFF.csproj&quot;, &quot;BFF/&quot;]
RUN dotnet restore &quot;BFF/BFF.csproj&quot;
COPY . .
WORKDIR &quot;/src/BFF&quot;
RUN dotnet build &quot;BFF.csproj&quot; -c Release -o /app/build

FROM build AS publish
RUN dotnet publish &quot;BFF.csproj&quot; -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT [&quot;dotnet&quot;, &quot;BFF.dll&quot;]
</code></pre>
</li>
<li>
<p><strong>Build the Docker Image</strong>:</p>
<pre><code class="language-bash">docker build -t bff-api .
</code></pre>
</li>
<li>
<p><strong>Run the Docker Container</strong>:</p>
<pre><code class="language-bash">docker run -d -p 8080:80 --name bff-api-container bff-api
</code></pre>
</li>
</ol>
<p>By containerizing the BFF, you enable consistent deployment and easy scaling across environments like development, staging, and production.</p>
<hr>
<h3 id="dockercomposefororchestratingmultipleservices"><strong>Docker Compose for Orchestrating Multiple Services</strong></h3>
<p><strong>Docker Compose</strong> simplifies running multi-container applications, allowing you to define and manage all services (BFF, backend services) in a single file.</p>
<h4 id="dockercomposeymlforbffandbackendservices"><strong><code>docker-compose.yml</code> for BFF and Backend Services:</strong></h4>
<pre><code class="language-yaml">version: '3'
services:
  bff-api:
    image: bff-api
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - &quot;8080:80&quot;
    depends_on:
      - backend-service1
      - backend-service2

  backend-service1:
    image: backend-service1
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
    ports:
      - &quot;8081:80&quot;

  backend-service2:
    image: backend-service2
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
    ports:
      - &quot;8082:80&quot;
</code></pre>
<ol>
<li><strong>Run Docker Compose</strong>:<pre><code class="language-bash">docker-compose up --build
</code></pre>
</li>
</ol>
<p>This command builds and runs the BFF and backend services as containers. <strong>Docker Compose</strong> simplifies orchestration by automatically starting all services, managing dependencies, and exposing the appropriate ports.</p>
<hr>
<h3 id="bringingitalltogetheryarpdockerinbffarchitecture"><strong>Bringing it All Together: YARP + Docker in BFF Architecture</strong></h3>
<ol>
<li><strong>Frontend Requests</strong>: Requests from different frontend applications (mobile, web) hit the BFF API.</li>
<li><strong>YARP</strong>: The BFF uses <strong>YARP</strong> to route these requests to the appropriate backend services based on predefined routes.</li>
<li><strong>Docker</strong>: The BFF and backend services run inside <strong>Docker</strong> containers, enabling scalable and consistent deployment across environments. Docker Compose helps orchestrate these services, ensuring smooth communication and interaction.</li>
<li><strong>Scaling and Deployment</strong>: With Docker, you can easily scale services horizontally (adding more instances of the BFF or backend services) and deploy them to container orchestration platforms like <strong>Kubernetes</strong> or <strong>Azure Kubernetes Service (AKS)</strong>.</li>
</ol>
<h3 id="prosandconsofyarpanddockerinbff"><strong>Pros and Cons of YARP and Docker in BFF</strong></h3>
<h4 id="pros"><strong>Pros:</strong></h4>
<ul>
<li><strong>Optimized for Frontend</strong>: BFF provides tailored APIs for each frontend, improving performance and reducing complexity.</li>
<li><strong>Scalability</strong>: Docker allows for easy scaling of individual services based on traffic.</li>
<li><strong>Centralized Routing</strong>: YARP efficiently handles routing and load balancing between backend services.</li>
<li><strong>Isolation and Portability</strong>: Docker ensures services are isolated and can run consistently in different environments.</li>
<li><strong>Simplified Deployment</strong>: Using Docker Compose simplifies the orchestration and deployment of multi-service applications.</li>
</ul>
<h4 id="cons"><strong>Cons:</strong></h4>
<ul>
<li><strong>Complexity</strong>: Adding YARP and Docker increases architectural complexity, especially when dealing with multiple services and deployment configurations.</li>
<li><strong>Overhead</strong>: Docker introduces additional resource overhead, and YARP might add latency due to its role as a proxy layer.</li>
<li><strong>Coordination Between Teams</strong>: Requires coordination between frontend, backend, and DevOps teams to ensure seamless integration and deployment.</li>
</ul>
<hr>
<h3 id="conclusion"><strong>Conclusion</strong></h3>
<p>Combining <strong>YARP</strong> and <strong>Docker</strong> in a <strong>Backend for Frontend (BFF)</strong> architecture provides a robust, flexible, and scalable solution. YARP simplifies request routing and load balancing, while Docker enables easy containerization, consistent deployments, and scaling across environments. Using Docker Compose further streamlines service orchestration, making it easier to manage and deploy multiple services. This setup allows for efficient handling of frontend-specific logic while keeping backend services isolated and scalable.</p>
</div>]]></content:encoded></item><item><title><![CDATA[I was accepted to speak at a conference! Now what?]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I started my public speaking journey about 3 years ago and I have learnt lots since then. In fact, before then I was really scared to speak in public. I'm not the most comfortable or proficient person on stage but things do get easier.</p>
<blockquote>
<p>I remember the very first time</p></blockquote></div>]]></description><link>https://passos.com.au/i-was-accepted-to-speak-at-a-conference-now-what/</link><guid isPermaLink="false">5d0319a271174c4460204edc</guid><category><![CDATA[Public Speaking]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Fri, 21 Jun 2019 07:51:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1475721027785-f74eccf877e2?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1475721027785-f74eccf877e2?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="I was accepted to speak at a conference! Now what?"><p>I started my public speaking journey about 3 years ago and I have learnt lots since then. In fact, before then I was really scared to speak in public. I'm not the most comfortable or proficient person on stage but things do get easier.</p>
<blockquote>
<p>I remember the very first time I delivered a talk. It was about Angular Routing in one of the <a href="http://angularhackday.com/">SSW Angular Hack Days</a>. Since then I did multiple meetups, hackdays, trainings and conferences around the world.</p>
</blockquote>
<p>I've heard a lot of people talking about public speaking and asking questions like what's involved? How to get started? Any tips? How is the process of putting together a talk? Do I need to be an expert on the subject? What about impostor syndrome? What if someone asks a question I don't know the answer, I'll feel stupid.</p>
<p>There are so many questions and so many doubts, and for someone who's in the comfort zone that will mean &quot;I'm not speaking in public&quot;, &quot;I'm not doing it&quot;, &quot;Hell NO!!!&quot;.</p>
<p>I'll say it again: I'm far from being the most proficient person in public speaking but here are a few things that worked and still work for me:</p>
<h4 id="howdoigetstarted">How do I get started?</h4>
<p>I started with lighting talks (15min) on subjects I was comfortable with and an audience I was familiar with. A great place to start is to propose a talk to one of the many meetups in your local area.</p>
<h4 id="doineedtobeanexpertonthesubject">Do I need to be an expert on the subject?</h4>
<p>I do <strong>NOT</strong> present only about things that I work on a daily basis or things I'm an expert about. In fact, a lot of times when I really want to push myself at learning about something, I put my head out and say I want to present about it, usually at local meetups. If they accept my talk then I start putting together the content and learn in the journey.</p>
<h4 id="whataboutimpostorsyndrome">What about impostor syndrome?</h4>
<p>That was a big thing for me when I started speaking. I was really scared to look dumb. I had this feeling that everyone in the room would know more than I did. But you know what, you don't need to pose as an expert in the field, you can be there to share something you are really excited about. You can be there to share YOUR unique experience and tell YOUR story. There's no better person at being you than you.</p>
<h4 id="thereisalreadysomuchcontentonxwhywouldanyonewanttohearitfromme">There is already so much content on 'x' why would anyone want to hear it from me?</h4>
<p>That's probably similar to the previous question but I think it's worth emphasising this point a bit more. People come to your talk to hear YOUR point of view about what you're presenting so they can relate to YOUR story.</p>
<p>I have seen in the past super smart people, creator of amazing products/technology, presenting on what they have created and, to be honest, sometimes I rather hear the story from someone using the product/technology, because their pain points will probably align more to mine.</p>
<h4 id="whatifsomeoneasksaquestionidontknowtheanswerto">What if someone asks a question I don't know the answer to?</h4>
<p>You'll never know everything. There's always going to be a time where someone asks something you don't know and it's fine not knowing the answer. There are a few ways you can get away with it. I try to go with the first one most of the time.</p>
<ol>
<li>&quot;Sorry, I don't know the answer. Is there anyone in the audience that knows about it?&quot;</li>
<li>&quot;Sorry, I don't know but I'll find out and come back to you about it.&quot;</li>
<li>&quot;That's a great question but it's out of the scope of this talk, why don't you come and see me afterwards?&quot; then you tell him/her you have no idea what he/she is talking about.</li>
</ol>
<h4 id="doigetnervousbeforetalks">Do I get nervous before talks?</h4>
<p>EVERY SINGLE TIME. It usually starts about 30 minutes before presenting and it escalates until the moment I say &quot;Hi, my name is Thiago Passos&quot; then I get into automatic mode.</p>
<h4 id="doipreparethetalkfirstthensubmitit">Do I prepare the talk first then submit it?</h4>
<p>That usually never happens unless it's not the first time I'm presenting it, otherwise, I only put together the title and abstract of a talk, if I do get accepted then I start preparing for the talk.</p>
<h4 id="howdoiputtogetheratalk">How do I put together a talk?</h4>
<p>That's probably a whole topic on its own, but let's see.</p>
<p>Once I know roughly what the content of my talk is, I try to create a flow or a story that makes sense in my head. For example, if I were to talk about a new front-end framework, I would start with the requirements for installing and using it. Then I would talk about some features of the framework that I think are exciting. And finally, I would provide resources where the audience could go to find out more about it.</p>
<p>I usually split the talk into 3 to 5 major sections and I work on then separately, by doing that I can always come back to a section and make improvements. It's also easier to rehearse and time each section at a time instead of the whole talk. Once I'm happy with a section, I move on to the next one.</p>
<blockquote>
<p>Here are some resources you might find useful for putting together your presentations: <a href="https://www.goodreads.com/book/show/3427627-slide">Slide:ology</a>, <a href="https://www.goodreads.com/book/show/1908456.Presentation_Zen">Presentation Zen</a></p>
</blockquote>
<h4 id="howdoipreparefortalks">How do I prepare for talks?</h4>
<p>Once I have the content in place, like the slide deck or a demo, I start rehearsing for the talk and that will include my introduction, all sections and the closing of the talk.</p>
<p>I usually record each section individually and time them. Doing these rehearsals is where I fine tune each section. <strong>The talk is never going to be perfect the first time, or the second or the hundredth, but it will definitely improve every single time.</strong></p>
<p>After I went through the talk multiple times, the talk is cemented in my brain and when I get in front of an audience, I almost turn into automatic mode.</p>
<h4 id="anytips">Any Tips?</h4>
<p>I saw this awesome <a href="https://twitter.com/kapehe_ok/status/1138853670571790337">thread</a> on Twitter started by <a href="https://twitter.com/kapehe_ok">@kapehe_ok</a> about &quot;What are the 3 MUST KNOW things you would tell a first-time conference speaker?&quot;. I'll compile some of the responses here with some of the things I think are helpful.</p>
<ul>
<li>Rehearse twice as much as you think is enough. Practice out loud to get used to hearing your own voice</li>
<li>Live demos can go wrong and will go wrong. Have a plan B: either record the demo and/or take screenshots through it.</li>
<li>If your demo does fail, spend 30sec MAX fixing it then blame the demo gods</li>
<li>The audience is on your side and they want you to succeed. Most of them would be terrified to be in your place and they respect that.</li>
<li>It's OK to be nervous</li>
<li>Breathe, pause, take your time. The pause in your head usually takes 10 times as much as it actually takes.</li>
<li>Keep water within arms reach and drink it</li>
<li>Find the nodders in the audience and make eye contact with them</li>
<li>Pee before you go up</li>
<li>Make sure you have all the necessary dongles and cables</li>
<li><strong>Do test runs with your colleagues/friends and ask for their feedback. Take the feedback and improve your talk then repeat.</strong></li>
</ul>
<h4 id="iwasacceptedtospeakataconferencenowwhat">I was accepted to speak at a conference! Now what?</h4>
<p>Own it!!! You got it!!!</p>
<p>Hope that helps. I still have loads to learn, but don't we all?</p>
<p>Cheers</p>
</div>]]></content:encoded></item><item><title><![CDATA[Can I Develop Az Functions on a Mac?]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I started writing this blog post a very long time ago when I switched for the first time in my life to a Mac as my main computer. I was super excited to have the shiniest laptop in town and I was sure I could do all my work on</p></div>]]></description><link>https://passos.com.au/az-funcions-on-mac/</link><guid isPermaLink="false">5b7ded0aa2758d11acf81bf3</guid><category><![CDATA[azure functions]]></category><category><![CDATA[Mac]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Fri, 14 Jun 2019 03:36:35 GMT</pubDate><media:content url="https://passos.com.au/content/images/2019/06/functionsLoveApple.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2019/06/functionsLoveApple.png" alt="Can I Develop Az Functions on a Mac?"><p>I started writing this blog post a very long time ago when I switched for the first time in my life to a Mac as my main computer. I was super excited to have the shiniest laptop in town and I was sure I could do all my work on it since most of what I did was .NET Core which is cross platform and front end development. So happy days!?!?!? Right? Not really!!!</p>
<p>I hit a massive road block, I found out that the tooling I needed to develop Azure Functions wasn't available for Mac and that was a deal breaker. Without the Storage Emulator, I could only run locally Azure Functions with HTTP triggers, and well within my solution that was the trigger I used the least.</p>
<blockquote>
<p>I WAS SAD; I stopped using the Mac for Azure Functions development but life goes on and there's no looking back.</p>
</blockquote>
<p>Time has passed and by accident I found out a community maintained open source storage emulator called <a href="https://github.com/azure/azurite">Azurite</a>. I was pumped and decided to give another chance for the Mac to be my main work machine again. So let's see how it goes.</p>
<p>First I installed Azurite by running <code>sudo npm install -g azurite</code>, once it was installed I kicked it off by simply running <code>azurite-blob</code>.</p>
<blockquote>
<p>If you haven't installed the Azure Functions Core Tools yet, do follow the steps from the <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#brew">Microsoft docs</a>.</p>
</blockquote>
<p>I put together a super simple code just to prove a point and to be able to share here and to create a Function App I went to the terminal and ran:</p>
<pre><code class="language-console">mkdir FunctionsOnMac
cd FunctionsOnMac
func init --worker-runtime dotnet
</code></pre>
<p>I also created two functions: a timer trigger and service bus trigger as below:</p>
<pre><code class="language-console">func new -t TimerTrigger -n TimerTrigger
func new -t ServiceBusQueueTrigger -n ServiceBusTrigger
</code></pre>
<p>Then I updated my <code>local.settings.json</code> as below to use the storage emulator just as you would in a Windows machine using the official <a href="https://docs.microsoft.com/en-us/azure/storage/common/storage-use-emulator">Storage Emulator</a>:</p>
<blockquote>
<p>For this example to work, it's required that you have an Azure Service Bus in place and a queue named <strong>temp</strong>. Also make sure you copy the service bus connection string and update the file below</p>
</blockquote>
<pre><code class="language-javascript">{
    &quot;IsEncrypted&quot;: false,
    &quot;Values&quot;: {
        &quot;AzureWebJobsStorage&quot;: &quot;UseDevelopmentStorage=true&quot;,
        &quot;AzureWebJobsDashboard&quot;: &quot;UseDevelopmentStorage=true&quot;,
        &quot;FUNCTIONS_WORKER_RUNTIME&quot;: &quot;dotnet&quot;,
        &quot;ServiceBusConnString&quot;: &quot;[SERVICE BUSS CONN STRING]&quot;
    }
}
</code></pre>
<blockquote>
<p>The idea of my sample app is for the timer to send a message to the service bus every 10 seconds and the service bus function to pick up that message and print in the screen, as simple as that.</p>
</blockquote>
<p>My timer function will look like this:</p>
<pre><code class="language-csharp">using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.WebJobs.ServiceBus;

namespace FunctionsOnMac
{
    public static class TimerTrigger
    {
        [FunctionName(nameof(RunTimer))]
        public static void RunTimer(
            [TimerTrigger(&quot;*/10 * * * * *&quot;)]TimerInfo myTimer, ILogger log,
            [ServiceBus(&quot;temp&quot;,EntityType.Queue,
            Connection = &quot;ServiceBusConnString&quot;)] 
            ICollector&lt;string&gt; messages)
        {
            log.LogInformation($&quot;C# Timer trigger function executed&quot;);
            messages.Add($&quot;Message sent from timer at {DateTime.Now}&quot;);
        }
    }
}
</code></pre>
<p>And my Service Bus function will look like the below:</p>
<pre><code class="language-csharp">using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;

namespace FunctionsOnMac
{
    public static class ServiceBusTrigger
    {
        [FunctionName(nameof(RunServiceBus))]
        public static void RunServiceBus(
            [ServiceBusTrigger(&quot;temp&quot;, Connection = &quot;ServiceBusConnString&quot;)]
            string message, 
            ILogger log){
                log.LogInformation($&quot;Message Received: '{message}'&quot;);
        }
    }
}
</code></pre>
<p>Now when I go back to the terminal and run <code>func start</code> that's what I get:<br>
<img src="https://passos.com.au/content/images/2019/06/function-working.png" alt="Can I Develop Az Functions on a Mac?"></p>
<p>Btw, if you do forget to run <code>azurite-blob</code>, you should be getting the below instead<br>
<img src="https://passos.com.au/content/images/2019/06/functions-not-working.png" alt="Can I Develop Az Functions on a Mac?"></p>
<p>Hoorey!!!! Happy days again!!! Let's wait for my next roadblock :D</p>
<p>Cheers</p>
</div>]]></content:encoded></item><item><title><![CDATA[Replacing Redis by Cosmos DB]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I've been using Redis for a while now and it just works, but I find particularly painful when I want to see what's cached without writing a custom tool for that. Another reason that drove me to try this solution was the fact that I'm paying too much for a</p></div>]]></description><link>https://passos.com.au/replacing-redis-by-cosmos-db/</link><guid isPermaLink="false">5d00792171174c4460204ece</guid><category><![CDATA[Azure]]></category><category><![CDATA[CosmosDb]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Thu, 13 Jun 2019 05:50:46 GMT</pubDate><media:content url="https://passos.com.au/content/images/2019/06/cosmos-db.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2019/06/cosmos-db.png" alt="Replacing Redis by Cosmos DB"><p>I've been using Redis for a while now and it just works, but I find particularly painful when I want to see what's cached without writing a custom tool for that. Another reason that drove me to try this solution was the fact that I'm paying too much for a Redis instance in Azure. So I thought I would give it a try and see how well Comos DB would perform in this situation.</p>
<p>The replacing process for me was actually quite simple since I had, to start with, an interface to deal with the cache called <code>ICacheHelper</code> and two implementation, one using <a href="https://en.wikipedia.org/wiki/Redis">Redis</a> and one using <a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.caching.memorycache?view=netframework-4.8">MemoryCache</a>. The interface itself was really simple:</p>
<pre><code class="language-csharp">public interface ICacheHelper
{
    Task Set&lt;T&gt;(string key, T obj, TimeSpan span);
    Task&lt;T&gt; TryGet&lt;T&gt;(string key);
    Task Remove(string key);
}
</code></pre>
<p>Now, all I needed to do is to create an implementation using Cosmos DB and update the DI configuration to use it.</p>
<p>To start with, I'll need to add the <a href="https://www.nuget.org/packages/Microsoft.Azure.DocumentDB.Core/">Cosmos DB package</a>. There are multiple ways to do it, I prefer using the command line by running <code>dotnet add package Microsoft.Azure.DocumentDB.Core</code>.</p>
<blockquote>
<p>I know I know, it says DocumentDB in the package. Microsoft will change it eventually.</p>
</blockquote>
<p>And here's how my implementation is going to look like. Just so it's simpler to understand, I'm going to break down the code in multiple parts and explain the process. For the constructor, all I'm passing is a custom object that contains all the settings I need to connect to a Cosmos DB instance. I'm also setting some defaults for the JsonSerializer, although this is not mandatory at all.</p>
<pre><code class="language-csharp">public CosmosCacheHelper(CosmosDbConfig config)
{
    _config = config;
    _jsonSerializer = new JsonSerializerSettings
    {
        NullValueHandling = NullValueHandling.Ignore,
        MissingMemberHandling = MissingMemberHandling.Ignore,
        ContractResolver = new CamelCasePropertyNamesContractResolver()
    };
    this._client = new DocumentClient(
        new Uri(_config.Endpoint), 
        _config.Key, 
        _jsonSerializer);
}
</code></pre>
<p>I've created a custom class to store the data I need in the cache. The <code>Id</code> and <code>Data</code> properties are pretty obvious, but what the heck is <code>Ttl</code>? TTL is <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/time-to-live">Time to Live</a> which is a property that will tell Cosmos DB to auto delete a document after a certain number of seconds.</p>
<pre><code class="language-csharp">private class CacheDocument&lt;T&gt;
{
    public string Id { get; set; }
    public long Ttl { get; set; }
    public T Data { get; set; }
}
</code></pre>
<p>The <code>Set</code> method will receive a key and the object itself and also a time to live as a <code>TimeSpan</code>. When I create the document, I'm coverting the TimeSpan into total seconds and setting the <code>Ttl</code> property.</p>
<pre><code class="language-csharp">public async Task Set&lt;T&gt;(string key, T item, TimeSpan span)
{
    var entry = new CacheDocument&lt;T&gt; {
        Data = item, 
        Id = key, 
        Ttl = Convert.ToInt64(span.TotalSeconds)};
    
    await _client.UpsertDocumentAsync(
        UriFactory.CreateDocumentCollectionUri(
            _config.DatabaseId, _config.CollectionId), entry,
        new RequestOptions {JsonSerializerSettings = _jsonSerializer});
}
</code></pre>
<p>Time to retrieve some data from the cache. If you know a bit of Cosmos DB, you might notice that I'm setting the <code>PartitionKey</code> the same as the key itself, which is not the most optimal way of using the <code>PartitionKey</code>, but that will do for now.</p>
<pre><code class="language-csharp">public async Task&lt;T&gt; TryGet&lt;T&gt;(string key)
{
    try
    {
        Document document =
          _client.ReadDocumentAsync(
              UriFactory.CreateDocumentUri(_config.DatabaseId,
                _config.CollectionId, key),
                new RequestOptions {
                PartitionKey = new PartitionKey(key)
            }).GetAwaiter().GetResult();
        var entry = (CacheDocument&lt;T&gt;) (dynamic) document;
        return entry.Data;
    }
    catch (DocumentClientException e)
    {
        if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
        { return default(T); }
        throw;
    }
}
</code></pre>
<p>Finally the <code>Remove</code> method which is not going to be used often, but here it is.</p>
<pre><code class="language-csharp">public async Task Remove(string key)
{
    await _client.DeleteDocumentAsync(
        UriFactory.CreateDocumentUri(
            _config.DatabaseId, _config.CollectionId, key), 
            new RequestOptions{PartitionKey = new PartitionKey(key)});
}
</code></pre>
<p>When it comes to the DI configuration, that's how mine is looking like, but for you it will really depend on what DI tool you're using.</p>
<pre><code class="language-csharp">builder.Register(a =&gt; new CosmosCacheHelper(new CosmosDbConfig
{
    Endpoint = GetEnvironmentVariable(&quot;CosmosCache_EndPoint&quot;),
    Key = GetEnvironmentVariable(&quot;CosmosCache_Key&quot;),
    DatabaseId = GetEnvironmentVariable(&quot;CosmosCache_DatabaseId&quot;),
    CollectionId = GetEnvironmentVariable(&quot;CosmosCache_CollectionId&quot;)
})).AsImplementedInterfaces();
</code></pre>
<p>And here's an example of how you can use it.</p>
<pre><code class="language-csharp">var key = $&quot;MyCachedCat{cat.Id}&quot;;
if ((await _cacheHelper.TryGet&lt;Cat&gt;(key)) != null)
{
    cached = new Cat { Id = cat.Id };
    await _cacheHelper.Set(key, cached, new TimeSpan(0, 1, 0, 0));
}
</code></pre>
<p>And just before I forget, in your Cosmos DB container, make sure you enable time to live, otherwise this solution won't work properly.<br>
<img src="https://passos.com.au/content/images/2019/06/setting-time-to-live.png" alt="Replacing Redis by Cosmos DB"></p>
<p>I'll evaluate this solution and update the post with some dollar amounts after a few weeks.</p>
<p>Hope it helps.</p>
<p>Cheers</p>
</div>]]></content:encoded></item><item><title><![CDATA[Converting JSON Objects into C# List<>]]></title><description><![CDATA[<div class="kg-card-markdown"><p>The problem I have is basically to fix some weird data structure coming from a data provider, which I'm sure there's some background story on why the structure is that way, into a reasonable structure that I can later on map into a model or entity more easily.</p>
<p>Let's start</p></div>]]></description><link>https://passos.com.au/converting-json-object-into-c-list/</link><guid isPermaLink="false">5c80726959bfdc2854bf9183</guid><category><![CDATA[JSON]]></category><category><![CDATA[Newtonsoft]]></category><category><![CDATA[C#]]></category><category><![CDATA[JsonConverter]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Thu, 07 Mar 2019 02:31:14 GMT</pubDate><media:content url="https://passos.com.au/content/images/2019/03/2019-03-07_12-23-40.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2019/03/2019-03-07_12-23-40.jpg" alt="Converting JSON Objects into C# List<>"><p>The problem I have is basically to fix some weird data structure coming from a data provider, which I'm sure there's some background story on why the structure is that way, into a reasonable structure that I can later on map into a model or entity more easily.</p>
<p>Let's start from the begining and go through some really basic examples when it comes to data binding. Imagine we have a JSON object which you're going to use as the body of a HTTP POST request.</p>
<pre><code class="language-javascript">{
	&quot;status&quot;: true,
	&quot;version&quot;: &quot;2.0.3&quot;,
	&quot;expires&quot;: &quot;1551829009.0&quot;
}
</code></pre>
<p>In the C# side, the parameter of the API can be as such and everything will be bound perfectly.</p>
<pre><code class="language-csharp">public class DataRequest
{
    public bool Status {get;set;}
    public string Version {get;set;}
    public TimeSpan Expires {get;set;}
}
</code></pre>
<p>What if now, we have a new field in the JSON object that doesn't really follow your naming convention, like the <code>status_code</code> below:</p>
<pre><code class="language-javascript">{
	&quot;status&quot;: true,
	&quot;version&quot;: &quot;2.0.3&quot;,
	&quot;status_code&quot;: 200,
	&quot;expires&quot;: &quot;1551829009.0&quot;
}
</code></pre>
<p>That's still not a big deal and with the <code>JsonProperty</code> attribute you can customize which field to bind to.</p>
<pre><code class="language-csharp">public class DataRequest
{
    public bool Status {get;set;}
    public string Version {get;set;}
    [JsonProperty(&quot;status_code&quot;)]
    public int StatusCode {get;set;}
    public TimeSpan Expires {get;set;}
}
</code></pre>
<p>The problem gets harder if the field name coming from the JSON object is dynamic. In my example, I have a scructure that's essentially an array, but it wasn't built this way and I have no access to change that so I have to work around it.</p>
<pre><code class="language-javascript">{
	&quot;status&quot;: true,
	&quot;version&quot;: &quot;2.0.3&quot;,
	&quot;status_code&quot;: 200,
	&quot;expires&quot;: &quot;1551829009.0&quot;,
    &quot;players&quot;:
    {
        &quot;a_hales&quot;: {
            &quot;seasonal_role&quot;: &quot;batsman&quot;,
            &quot;fullname&quot;: &quot;Alex Hales&quot;,
            &quot;name&quot;: &quot;AD Hales&quot;
        },
        &quot;j_root&quot;: {
            &quot;seasonal_role&quot;: &quot;batsman&quot;,
            &quot;fullname&quot;: &quot;Joe Root&quot;,
            &quot;name&quot;: &quot;JE Root&quot;
        },
        &quot;d_bishoo&quot;: {
            &quot;seasonal_role&quot;: &quot;bowler&quot;,
            &quot;fullname&quot;: &quot;Devendra Bishoo&quot;,
            &quot;name&quot;: &quot;Devendra Bishoo&quot;
        }
    }
}
</code></pre>
<p>Using <code>JsonProperty</code> is out of the equation because I have no idea what the property name for each individual player is going to be, also at the end of the day I want to bind that property name to the value of an <code>Id</code> field. Another tricky point is the fact that's <code>players</code> is an object and I want it to be an array.</p>
<h5 id="herecomesthesolution">Here comes the solution</h5>
<p>I want each individual player inside that <code>players</code> object to be bound to this C# class.</p>
<pre><code class="language-csharp">public class Player
{
    public string Id { get; set; }
    [JsonProperty(&quot;seasonal_role&quot;)] 
    public string SeasonalRole { get; set; }
    public string Fullname { get; set; }
    public string Name { get; set; }
}
</code></pre>
<p>The <code>DataRequest</code> would look something along these lines, but the <code>Players</code> property wouldn't be bound as it expects a JSON array instead of an object.</p>
<pre><code class="language-csharp">public class DataRequest
{
    public bool Status {get;set;}
    public string Version {get;set;}
    [JsonProperty(&quot;status_code&quot;)]
    public int StatusCode {get;set;}
    public TimeSpan Expires {get;set;}
    public List&lt;Player&gt; Player {get;set;}
}
</code></pre>
<p>There it comes a custom <code>JsonConverter</code> from <a href="https://www.nuget.org/packages/Newtonsoft.Json/">Newtonsoft</a> for the win.</p>
<pre><code class="language-csharp">public class PlayersConverter : JsonConverter
{
    // This is used when you're converting the C# List back to a JSON format
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteStartArray();
        foreach (var player in (List&lt;Player&gt;) value)
        {
            writer.WriteRawValue(JsonConvert.SerializeObject(player));
        }
        writer.WriteEndArray();
    }

    // This is when you're reading the JSON object and converting it to C#
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var response = new List&lt;Player&gt;();
        // Loading the JSON object
        JObject players = JObject.Load(reader);
        // Looping through all the properties. C# treats it as key value pair
        foreach (var player in players)
        {
            // Finally I'm deserializing the value into an actual Player object
            var p = JsonConvert.DeserializeObject&lt;Player&gt;(player.Value.ToString());
            // Also using the key as the player Id
            p.Id = player.Key;
            response.Add(p);
        }

        return response;
    }

    public override bool CanConvert(Type objectType) =&gt; objectType == typeof(List&lt;Player&gt;);
}
</code></pre>
<p>And to close the loop, we need to update the <code>DataRequest</code> to use this new converter.</p>
<pre><code class="language-csharp">public class DataRequest
{
    public bool Status {get;set;}
    public string Version {get;set;}
    [JsonProperty(&quot;status_code&quot;)]
    public int StatusCode {get;set;}
    public TimeSpan Expires {get;set;}
    [JsonConverter(typeof(PlayersConverter))]
    public List&lt;Player&gt; Player {get;set;}
}
</code></pre>
<p>I hope it helps.</p>
<p>Cheers</p>
</div>]]></content:encoded></item><item><title><![CDATA[Postman - Data Driven API requests]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I've always known that <a href="https://www.getpostman.com/">Postman</a> is powerful tool and a must for any developer and for several years I've been using it just for some trivial testing, nothing too crazy until I found the need to actually do something more meaningful with it.</p>
<p>Recently I had the need to ingest</p></div>]]></description><link>https://passos.com.au/postman-data-drive-requests/</link><guid isPermaLink="false">5c7511f98ed830196012ff9c</guid><category><![CDATA[postman]]></category><category><![CDATA[api first]]></category><category><![CDATA[API]]></category><category><![CDATA[REST API]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Tue, 26 Feb 2019 10:46:51 GMT</pubDate><media:content url="https://passos.com.au/content/images/2019/02/postman.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2019/02/postman.jpg" alt="Postman - Data Driven API requests"><p>I've always known that <a href="https://www.getpostman.com/">Postman</a> is powerful tool and a must for any developer and for several years I've been using it just for some trivial testing, nothing too crazy until I found the need to actually do something more meaningful with it.</p>
<p>Recently I had the need to ingest a whole lot of data into the system I'm working on, but the data was scattered across the system and pushing the data straight into the database would be a nightmare.</p>
<p>I already had the process to ingest the data one by one through an API and I didn't want to build something to ingest in bulk just to be used once in a life time. I knew Postman was probably the answer so I started digging.</p>
<p>I found this guys, <a href="https://www.youtube.com/channel/UCUUl_HXJjU--iYjUkIgEcTw">Valentin Despa</a>, on YouTube showing how you can automate a bunch of API calls using pre-request script to loop through a javascript array and updating environment variables on the way. That was a great start, but it wasn't the full answer as I had a JSON file with thousands of objects that I wanted to turn into requests.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/sZKoBQ1HpKw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>Then I found this other guy, <a href="https://www.youtube.com/channel/UCTt7pyY-o0eltq14glaG5dg">Raghav Pal</a>, also on YouTube showing exactly what I was looking for which was ingesting the API requests using a JSON file.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/BzHsDihOlfY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>Just to keep a record here how much I appreciate people sharing their work. It makes my life so much easier and I bet it makes yours too :D</p>
<p>Cheers</p>
</div>]]></content:encoded></item><item><title><![CDATA[No Excuses Not To Have E2E Tests]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I haven't always been a big tester, I started finding the great advantages of unit testing the back end just a couple of years ago. With E2E tests I barely got close, everything I tried was so fiddly and use to break so easily. Well, <a href="https://www.cypress.io/">Cypress</a> changed my way of</p></div>]]></description><link>https://passos.com.au/no-excuses-not-to-have-e2e-tests/</link><guid isPermaLink="false">5c3dce505fbdd734cc7f4ec9</guid><category><![CDATA[e2e test]]></category><category><![CDATA[integration test]]></category><category><![CDATA[cypress]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Tue, 15 Jan 2019 12:37:51 GMT</pubDate><media:content url="https://passos.com.au/content/images/2019/01/Rec-01-15-19_screen_video.00_00_05_16.Still001.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2019/01/Rec-01-15-19_screen_video.00_00_05_16.Still001.jpg" alt="No Excuses Not To Have E2E Tests"><p>I haven't always been a big tester, I started finding the great advantages of unit testing the back end just a couple of years ago. With E2E tests I barely got close, everything I tried was so fiddly and use to break so easily. Well, <a href="https://www.cypress.io/">Cypress</a> changed my way of thinking. It's so powerful and so simple to get started.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/bZ0cuE3vu7w" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<ol>
<li>Let's see how simple it is. I'm going to start with an empty folder and from the console I'll run <code>npm i cypress --save-dev</code></li>
<li>We need now to create a file called <code>cypress.json</code> which will hold all the configuration for Cypress, all I'm going to put inside for now is <code>{}</code>, an empty JSON object.</li>
<li>Inside the current directory I'm going to create a folder called <code>cypress</code>, and <code>integration</code> inside cypress. The integration folder will hold all test specs.</li>
<li>Now just as a test, I'll create a new file called <code>google_spec.js</code> and inside the file I'll add the below:</li>
</ol>
<pre><code class="language-javascript">describe(&quot;Google Thiago Passos&quot;, function() {
  it(&quot;Should search Thiago Passos' Blog&quot;, function() {
    cy.visit(&quot;https://www.google.com.au&quot;);
    cy.get(&quot;input[type=text]:first&quot;).type(&quot;Thiago Passos&quot;);
    cy.get(&quot;input[type=submit]:first&quot;).click();
    cy.get(&quot;.g:first a:first&quot;).click();
    cy.url().should(&quot;include&quot;, &quot;passos.com.au&quot;);
  });
});
</code></pre>
<ol start="5">
<li>To run the tests, we can simply run <code>cypress run</code>. This will run all the tests in the console without opening a browser.</li>
</ol>
<p><img src="https://passos.com.au/content/images/2019/01/running-console.jpg" alt="No Excuses Not To Have E2E Tests"></p>
<ol start="6">
<li>To run the tests in the browser, you can run <code>cypress open --project .</code>. This will open cypress console and you can press on <strong>Run all specs</strong></li>
</ol>
<p><img src="https://passos.com.au/content/images/2019/01/cypress-open.jpg" alt="No Excuses Not To Have E2E Tests"><br>
Which will run all the tests in an interactive way.<br>
<img src="https://passos.com.au/content/images/2019/01/cypress-browser.jpg" alt="No Excuses Not To Have E2E Tests"></p>
<p>Hope you now share my excitment about e2e tests.</p>
<p>Cheers</p>
</div>]]></content:encoded></item><item><title><![CDATA[Mocking your API with JSON Server]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I've just learned recently about this NPM package to mock a REST API using a JSON file. That's a life changer and I see myself using it for any front-end development I do in the future.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/nwtGhyomKCY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>It's super simple to get started, all you'll need is <a href="https://nodejs.org/en/">Node.js</a> installed and</p></div>]]></description><link>https://passos.com.au/mocking-your-api-with-json-server/</link><guid isPermaLink="false">5c35840f71b44e2158bcdb27</guid><category><![CDATA[JSON]]></category><category><![CDATA[API]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Mock]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Wed, 09 Jan 2019 05:29:39 GMT</pubDate><media:content url="https://passos.com.au/content/images/2019/01/JsonServer.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2019/01/JsonServer.jpg" alt="Mocking your API with JSON Server"><p>I've just learned recently about this NPM package to mock a REST API using a JSON file. That's a life changer and I see myself using it for any front-end development I do in the future.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/nwtGhyomKCY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>It's super simple to get started, all you'll need is <a href="https://nodejs.org/en/">Node.js</a> installed and <a href="https://www.npmjs.com/package/json-server">json-server</a>.</p>
<p>To install json-server:</p>
<pre><code class="language-console">npm i -g json-server
</code></pre>
<p>Now all you need is a JSON file and run json-server against it. So try with this one and save it as <code>db.json</code></p>
<pre><code class="language-javascript">{
  &quot;products&quot;: [
    {
      &quot;name&quot;: &quot;apple&quot;,
      &quot;categoryId&quot;: 1,
      &quot;id&quot;: 1
    },
    {
      &quot;name&quot;: &quot;banana&quot;,
      &quot;categoryId&quot;: 1,
      &quot;id&quot;: 2
    },
    {
      &quot;name&quot;: &quot;watermelon&quot;,
      &quot;categoryId&quot;: 1,
      &quot;id&quot;: 3
    },
    {
      &quot;name&quot;: &quot;orange&quot;,
      &quot;categoryId&quot;: 1,
      &quot;id&quot;: 4
    }
  ],
  &quot;categories&quot;: [
    {
      &quot;name&quot;: &quot;fruit&quot;,
      &quot;id&quot;: 1
    }
  ]
}
</code></pre>
<p>Time to run it. I'm using the <code>--watch</code> flag so any change made directly to the <code>db.json</code> file gets reflected to the server</p>
<pre><code class="language-console">json-server db.json --watch
</code></pre>
<p>And that's all. You can try it out in the browser <code>http://localhost:3000/products</code> for example.</p>
<p>Cheers</p>
</div>]]></content:encoded></item><item><title><![CDATA[Deploying Vue.js to Azure Static Website]]></title><description><![CDATA[<div class="kg-card-markdown"><p>We've been putting together the content for the <a href="https://ndc-london.com/workshop/mastering-vue-js/">workshop</a> <a href="https://twitter.com/jasongtau">Jason</a> and I will run at <a href="https://ndc-london.com">NDC London</a> in January and part of the story is the deployment and why not using something brand new which is the Azure Static Websites? So let's give it a go.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/6xM7bSRARZM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h3 id="creatingbrandnewvuejsapp">Creating brand new Vue.</h3></div>]]></description><link>https://passos.com.au/deploying-vue-js-to-azure-static-websites/</link><guid isPermaLink="false">5c0da8df25618940941ad8eb</guid><category><![CDATA[Azure]]></category><category><![CDATA[VueJs]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Tue, 11 Dec 2018 07:15:13 GMT</pubDate><media:content url="https://passos.com.au/content/images/2018/12/running-locally-2.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2018/12/running-locally-2.jpg" alt="Deploying Vue.js to Azure Static Website"><p>We've been putting together the content for the <a href="https://ndc-london.com/workshop/mastering-vue-js/">workshop</a> <a href="https://twitter.com/jasongtau">Jason</a> and I will run at <a href="https://ndc-london.com">NDC London</a> in January and part of the story is the deployment and why not using something brand new which is the Azure Static Websites? So let's give it a go.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/6xM7bSRARZM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h3 id="creatingbrandnewvuejsapp">Creating brand new Vue.js app</h3>
<p>I'm going to use the <a href="https://cli.vuejs.org/">Vue Cli</a> in my example. So it's as simple as going to the command line and typing <code>vue create myvueapp</code>. From here you can choose what sort of features you want, in my case I've got a preset with <code>vue-router</code>, <code>vuex</code>, <code>babel</code> and <code>eslint</code>, but the feature I want to highlight in this post is the router, so make sure you enable this one.</p>
<p><img src="https://passos.com.au/content/images/2018/12/vue-create.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
<p>Once the app is created, I can start it with this command <code>npm run serve</code> and I've got my app running locally. Too easy!!!<br>
<img src="https://passos.com.au/content/images/2018/12/running-locally.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
<h3 id="creatingstaticwebsiteinazure">Creating static website in Azure</h3>
<p>Okay, let's leave the app as is for now and let's jump into the <a href="https://portal.azure.com">Azure portal</a> and create our first static website.</p>
<ol>
<li>First we're going to create a storage account. <strong>&quot;Hold on!!! I thought we were creating a website&quot;</strong>. Calm down! We'll get there.</li>
</ol>
<p><img src="https://passos.com.au/content/images/2018/12/createing-storage.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
<ol start="2">
<li>Make sure you select the <strong>StorageV2</strong>. The Static Website feature is still in preview and it's only available on this storage account type.</li>
</ol>
<p><img src="https://passos.com.au/content/images/2018/12/selecting-v2.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
<ol start="3">
<li>Now it's time to enable the static website feature. The cool thing about this feature is that it also has an error document path which we will also point to <code>index.html</code> and let Vue handle any routing issue. Take note of the primary endpoint here as it's going to be your initial url.</li>
</ol>
<p><img src="https://passos.com.au/content/images/2018/12/enabling-static-website.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
<h3 id="buildinganddeployingourvuejsappmanually">Building and deploying our Vue.js app manually</h3>
<p>Again we're going to use the Vue Cli and also the <a href="https://azure.microsoft.com/en-au/features/storage-explorer/">Storage Explorer</a></p>
<ol>
<li>Let's first build it with the command <code>npm run build</code>, after running it, you'll see a <code>dist</code> folder inside your app folder</li>
</ol>
<p><img src="https://passos.com.au/content/images/2018/12/dist-folder.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
<ol start="2">
<li>We're going to copy the content of the dist folder to the <code>$web</code> folder in your storage account using the <a href="https://azure.microsoft.com/en-au/features/storage-explorer/">Storage Explorer</a>. You can basically drag-and-drop if you want.</li>
</ol>
<p><img src="https://passos.com.au/content/images/2018/12/paste-content-to-web-folder.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
<ol start="3">
<li>Let's test it out and go to the URL we've copied previously. <strong>Hooray!!</strong> You can test the routing and see that everything is working perfectly.<br>
<img src="https://passos.com.au/content/images/2018/12/vue-running-in-azure.jpg" alt="Deploying Vue.js to Azure Static Website"></li>
</ol>
<h3 id="buildinganddeployingautomaticallywithazuredevops">Building and deploying automatically with Azure DevOps</h3>
<p>To do a quick and dirty test, building and deploying your application manually is fine, but it's really not maintainable for a real app. So let's configure a quick CI/CD for it.</p>
<p>I'll assume your source code is either in a git repository on Azure DevOps or somewhere accessible. In my case, I'm going to use a git repository within Azure DevOps.</p>
<ol>
<li>
<p>Creating a new pipeline<br>
<img src="https://passos.com.au/content/images/2018/12/build-1.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
</li>
<li>
<p>Selecting the repository<br>
<img src="https://passos.com.au/content/images/2018/12/build-2.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
</li>
<li>
<p>We'll start with an empty job in this case<br>
<img src="https://passos.com.au/content/images/2018/12/build-3.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
</li>
<li>
<p>We're going to create 2 npm steps, the first one is a normal <code>npm install</code><br>
<img src="https://passos.com.au/content/images/2018/12/build-4.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
</li>
<li>
<p>The second one is going to use the custom with <code>build</code> parameter<br>
<img src="https://passos.com.au/content/images/2018/12/build-5.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
</li>
<li>
<p>We should use separate CI and CD pipelines, but just for sake of simplicity, I'm just going to add the step to do the deployment here in the CI pipeline. We're going to search for the <code>Azure File Copy</code> step<br>
<img src="https://passos.com.au/content/images/2018/12/build-6.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
</li>
<li>
<p>A very important thing to mention here is to use the version <code>2.* (preview)</code>, otherwise it's going to fail when trying to copy to the <code>$web</code> folder.<br>
<img src="https://passos.com.au/content/images/2018/12/build-7-1.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
</li>
<li>
<p>Time to <strong>save &amp; queue</strong><br>
<img src="https://passos.com.au/content/images/2018/12/build-8.jpg" alt="Deploying Vue.js to Azure Static Website"></p>
</li>
</ol>
<p>And voila! Your app is now being built and deployed automatically.</p>
<blockquote>
<p>One thing to keep in mind here: if you're using YAML and a Windows machine as the build agent, you might encounter some issues if you have multiple commands in a single task. Check <a href="https://stackoverflow.com/questions/56571383/azure-static-web-site-build-pipeline-blob-upload">Ron's question on Stack Overflow</a> for more information</p>
</blockquote>
<p>Hope it helps!</p>
<p>Cheers</p>
</div>]]></content:encoded></item><item><title><![CDATA[... Next steps]]></title><description><![CDATA[<div class="kg-card-markdown"><p>It's time to move on. I left a small consultancy a bit over 4 years ago in Melbourne to move to Brisbane and join this awesome company that is <a href="https://www.ssw.com.au">SSW</a>, but an unrefusable opportunity came up and I couldn't let it pass.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/US5psWFlSDU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>I've loved working for SSW and all the</p></div>]]></description><link>https://passos.com.au/next-steps/</link><guid isPermaLink="false">5b92386eaabc481be0f62ef6</guid><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Thu, 29 Nov 2018 00:42:44 GMT</pubDate><media:content url="https://passos.com.au/content/images/2018/09/SSWWhite_Logo.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2018/09/SSWWhite_Logo.png" alt="... Next steps"><p>It's time to move on. I left a small consultancy a bit over 4 years ago in Melbourne to move to Brisbane and join this awesome company that is <a href="https://www.ssw.com.au">SSW</a>, but an unrefusable opportunity came up and I couldn't let it pass.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/US5psWFlSDU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>I've loved working for SSW and all the people that make it such a great place to work, and I have to say that it was a really hard decision to make but since I had my third child I feel an even stronger need to be home more often, so that's what I'm seeking, reducing my commute time by working from home.</p>
<p>Before I joined SSW, <a href="https://twitter.com/AdamCogan">Adam Cogan</a>, my boss to be, told me that when it was time for me to leave I should come back to him and tell him what I learnt and how much I grew during my stay there. This really took me by surprise because that was the first time I started in a company where the boss had the peace of mind that eventually I'd be moving on. That was an awesome start...</p>
<p>I learnt so many things at SSW in the past 4 years that it's going to be really hard to mention everything, but it was definitely a milestone in my life and my career, where I started as a mediocre developer and now I have the confidence to call myself a solution architect.</p>
<h2 id="whatdidilearn">What did I learn?</h2>
<ul>
<li>I got a grasp of what <a href="https://www.ssw.com.au/ssw/Consulting/Scrum.aspx">Scrum</a> is all about</li>
<li>I left that SharePoint bubble to expand my horizon with pure web and cloud development</li>
<li>I learned front end development which I wasn't used to</li>
<li>Continuous integration and continuous deployment? I didn't even know what it meant and now I could even call myself a &quot;DevOps&quot; expert.</li>
<li>So many development good practices that the list would be way too long to put here</li>
<li>The power of <a href="https://rules.ssw.com.au/do-you-understand-the-value-of-consistency">consistency</a></li>
<li>That the rules are not such a bad thing</li>
<li>The feedback culture. Don't worry, I'm still going to call out for a <a href="https://rules.ssw.com.au/conduct-a-test-please-internally-and-then-with-the-client"><em>test please</em></a> before I give a talk</li>
<li>Lost the fear of public speaking and I actually got to like it</li>
<li>The importance of sharing knowledge</li>
<li><a href="https://rules.ssw.com.au/record-a-quick-and-dirty-done-video"><em>Done videos</em></a> are so useful</li>
<li>The value of <a href="https://passos.com.au/how-to-turn-a-nasty-email-into-a-win/">being compassionate with the client's struggles</a></li>
<li>To be a better consultant with all the above</li>
</ul>
<hr>
<h2 id="afewveryspecialthanks">A few very special thanks</h2>
<p><a href="https://twitter.com/AdamCogan">Adam</a> and <a href="https://twitter.com/anastasiacogan">Ana</a>, thank you very much for having me for the last 4 years, not only in the company, but also in your family. I've learned heaps from you two, not only as a professional but as a human being.</p>
<p><a href="https://twitter.com/jasongtau">Jason</a> and <a href="https://twitter.com/BrendanSSW">Brendan</a>, you are the super stars of the best SSW office (Brisbane). It was an honour to be in so many projects with you two, keep it up with the great work you guys are doing and I'm sure we're going to work together in the future.</p>
<p><a href="https://twitter.com/adamstephensen">Stevo (Adam from Microsoft)</a>, even though you're no longer with SSW, I had to mention you here, you've been a mentor and a friend for most of my stay at SSW until you left us for Microsoft, but I still love you.</p>
<p><a href="https://twitter.com/dunchunter">Duncan</a>, you were an inspiration for me to get started in the public speaking journey.</p>
<p><a href="https://twitter.com/ulyssesmac">Uly</a>, I remember like it was yesterday, I was doing the communication test with you and we probably spent half the time talking about dancing. I went back home very excited to talk to Alyne and tell her I might join a company where they have a salsa champion.</p>
<p><a href="https://twitter.com/LCorfieldSSW">Lindsay</a>, I remember when I was introduced to you, I was asking Adam if I'd need now to report to you and he called you into the conversation, I thought it would be most awkward chat, but you were great. You've been doing a great job with the Brisbane office and taking Chinafy to the world, keep it up.</p>
<p><a href="https://twitter.com/geo_ge">Gabe</a>, we worked together for such a small period of time but it was great fun, we even managed to start our own <a href="https://www.youtube.com/channel/UCHYi-ucclDksXxMOUTgyixQ">YouTube Channel</a>. Your enthusiasm is amazing. Looking forward to more chats and coffees when I'm in Melbourne.</p>
<p><a href="https://twitter.com/patribarros1">Patricia</a>, keep your smiles up, you bring light to every room you come in. <a href="https://twitter.com/ChrisWBeaver">Chris</a>, your optimism is mind blowing. <a href="https://twitter.com/jernej_kavka">JK</a>, keep it up and don't forget to dance. Rebs, you make our lives so much easier with your magic wand that makes everything look good. And to all of you that were with me in that big project, thank you very much for your patiance, it was a hard gig, quite stressful several times, but we made it.</p>
<p><a href="https://twitter.com/Jean_SSW">Jean</a> and <a href="https://twitter.com/LiamElliott_au">Liam</a>, you guys have a duty to keep the community stuff happening in Sydney, keep you talks going.</p>
<p>New, thank you for always being so patient explaining about payment stuff.</p>
<p><a href="https://twitter.com/JoannaFeely">Jo</a> and <a href="https://twitter.com/PennyWalker_SSW">Penny</a>, thank you so much for all the hard work to put together all the events that we did in the last couple of years. It wouldn't be the same without you.</p>
<p><a href="https://twitter.com/raj_dhatt">Raj</a>, thanks for making us look good on camera. I learned lots with you and hopefully we'll have shots soon.</p>
<p>Sorry I didn't mention everyone, SSW has plenty of great and smart people. It was an amazing journey and I loved being part of the SSW family.</p>
<p>Cheers</p>
</div>]]></content:encoded></item><item><title><![CDATA[Rich Text Editor for Angular]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Okay, so I've published my very first <code>npm</code> package. Let's start from the beginning. We've got an internal MVC application that uses the <a href="https://www.telerik.com/aspnet-mvc">Kendo UI from Progress</a>, the rich text editor to be more specific. We started migrating that application to Angular and, to our surprise, the editor was not</p></div>]]></description><link>https://passos.com.au/rich-text-editor-for-angular/</link><guid isPermaLink="false">5bf4ea58dd38ed22a89b7fc6</guid><category><![CDATA[angular]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Wed, 21 Nov 2018 06:16:59 GMT</pubDate><media:content url="https://passos.com.au/content/images/2018/11/splash.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2018/11/splash.png" alt="Rich Text Editor for Angular"><p>Okay, so I've published my very first <code>npm</code> package. Let's start from the beginning. We've got an internal MVC application that uses the <a href="https://www.telerik.com/aspnet-mvc">Kendo UI from Progress</a>, the rich text editor to be more specific. We started migrating that application to Angular and, to our surprise, the editor was not available for Angular as yet.</p>
<p>What do we do from here? One of the options was to use the <a href="https://www.telerik.com/kendo-jquery-ui">Kendo UI for jQuery</a> and hack it so it works with Angular. Well, I know that sounds dirty and half way through doing it, I had the wash my hands and say &quot;That's enough!&quot;.</p>
<p>We then called <a href="https://twitter.com/JohnBristowe">John Bristowe</a> from Progress to help us make a decision on that and, to our surprise, he suggested us to try <a href="https://prosemirror.net/">ProseMirror</a> as both Progress and <a href="https://www.devexpress.com/">DevExpress</a> were evaluating/using it. I gave it a go and looked beautiful. I've even found a great implementation for <a href="https://vuejs.org/">Vue.js</a> called <a href="https://tiptap.scrumpy.io/">tiptap</a>.</p>
<p>One of the beauties of ProseMirror is that it handles markdown quite elegantly and that's why we wanted to leverage it.</p>
<p>The problem was I couldn't find an easy implementation for Angular which made my life quite hard but interesting. So I decided to roll my own npm package to simplify the implementation and maybe to help others out there.</p>
<blockquote>
<p>Here it is: <a href="https://www.npmjs.com/package/ngx-prosemirror">https://www.npmjs.com/package/ngx-prosemirror</a></p>
</blockquote>
<p>Be aware it's really really really early days, I just published and there's a lot of work to do. If you want to help me out, go to the <a href="https://github.com/thiagospassos/ngx-prosemirror">GitHub repository</a> and give it a go.</p>
<h3 id="instructions">Instructions</h3>
<ul>
<li><code>npm i ngx-prosemirror</code></li>
<li>Import the <code>NgxProsemirrorModule</code> to your module</li>
<li>Add to your component</li>
</ul>
<pre><code class="language-html">&lt;ngx-prosemirror [(content)]=&quot;contentInMarkdown&quot;&gt;&lt;/ngx-prosemirror&gt;
</code></pre>
<p>Hope it helps.</p>
<p>Cheers.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Push Notifications + Angular + .NET Core]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I'm sure 100% of you have already heard about what a push notification is, that's what apps and several web sites do to get your attention even if you don't have their app/website open. But the implementation of it might seem a little bit of magic for some. In</p></div>]]></description><link>https://passos.com.au/push-notifications-angular-net-core/</link><guid isPermaLink="false">5bb552e594af2d3a50b200e0</guid><category><![CDATA[angular]]></category><category><![CDATA[swagger]]></category><category><![CDATA[nswag]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[service worker]]></category><category><![CDATA[push notification]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Thu, 04 Oct 2018 04:06:41 GMT</pubDate><media:content url="https://passos.com.au/content/images/2018/10/jamie-street-493146-unsplash-2.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2018/10/jamie-street-493146-unsplash-2.jpg" alt="Push Notifications + Angular + .NET Core"><p>I'm sure 100% of you have already heard about what a push notification is, that's what apps and several web sites do to get your attention even if you don't have their app/website open. But the implementation of it might seem a little bit of magic for some. In this post, I'm going to cover how to implement push notifications in an Angular application and also how to handle these notifications differently when you have the app open.</p>
<blockquote>
<p>As always, the source code covered in this post is available <a href="https://github.com/thiagospassos/push-notification-angular-dotnet-core">here</a></p>
</blockquote>
<p>Push notifications in a web site is now possible due to service workers, which is something that's going to run in the background even if you don't have the website open. If you haven't heard about what service workers are, check out this <a href="https://passos.com.au/service-workers/">post</a>.</p>
<p>To get started, I'm going to use the .NET Core + Angular SPA template (if you want to know more about this template, check this <a href="http://www.codingflow.net/building-single-page-applications-on-asp-net-core-2-1-with-angular-6-part-1-getting-started/">post</a>). Here are the very simple commands:</p>
<pre><code class="language-powershell">mkdir push-notification-angular-dotnet-core
cd push-notification-angular-dotnet-core
dotnet new angular
</code></pre>
<h2 id="heresourplanthestuffweneedtoputtogether">Here's our plan, the stuff we need to put together:</h2>
<ol>
<li>A service worker in place to handle push events when the site is not open</li>
<li>Register the service worker</li>
<li>Create Public and Private keys</li>
<li>A way to subscribe and unsubscribe to push events on demand. Basically, ask permission from the user to send push events.</li>
<li>Handling push events</li>
<li>Handling subscriptions in the server</li>
<li>Send push events from the server</li>
</ol>
<hr>
<h3 id="1createourserviceworker">#1 Create our service worker</h3>
<p>For now, we're just going to create a <code>sw.js</code> file in the assets folder. This is going to be our service worker.</p>
<h3 id="2registerourserviceworker">#2 Register our service worker</h3>
<p>Since we're using Angular, I want to do this in the neatest way possible, so I'm going to create a service for this using the <a href="https://cli.angular.io/">Angular Cli</a>:</p>
<pre><code class="language-powershell">cd ClientApp\src\app
ng g s core/notificationMiddleware
</code></pre>
<p>In the service, I'm going to create a few things. First I'm going to create an object to hold the state of push notification of the browser instance.</p>
<pre><code class="language-javascript">public pushNotificationStatus = {
    isSubscribed: false,
    isSupported: false
  };
</code></pre>
<p>An <code>init</code> method which I'm going to call from the <code>app.component</code> as soon as the app starts which is going to check if the current browser supports service workers and push notifications, if it does then we're going to automatically register the service worker (<code>sw.js</code>) we added to our assets folder. Also a <code>checkSubscription</code> method to update the <code>isSubscribed</code> flag.</p>
<pre><code class="language-javascript">init() {
  if ('serviceWorker' in navigator &amp;&amp; 'PushManager' in window) {
    navigator.serviceWorker.register('/assets/sw.js')
      .then(swReg =&gt; {
        console.log('Service Worker is registered', swReg);

        this.swRegistration = swReg;
        this.checkSubscription();
      })
      .catch(error =&gt; {
        console.error('Service Worker Error', error);
      });
    this.pushNotificationStatus.isSupported = true;
  } else {
    this.pushNotificationStatus.isSupported = false;
  }
}

checkSubscription() {
  this.swRegistration.pushManager.getSubscription()
    .then(subscription =&gt; {
      console.log(subscription);
      console.log(JSON.stringify(subscription));
      this.pushNotificationStatus.isSubscribed = !(subscription === null);
    });
}
</code></pre>
<p>Now in the <code>app.component.ts</code>, we're going to inject the <code>NotificationMiddlewareService</code> and call the <code>init</code> method:</p>
<pre><code class="language-javascript">export class AppComponent implements OnInit {
  title = 'app';

  constructor(private notificationMiddleware: NotificationMiddlewareService) {
  }

  ngOnInit(){
    this.notificationMiddleware.init();
  }
}
</code></pre>
<p>If we run <code>ng serve</code> in the <code>ClientApp</code> folder and go to the browser now, we should see the service worker installed by going to the <em>Dev Tool bar &gt; Application &gt; Service Workers</em>.</p>
<blockquote>
<p>While in development stage, keep the flag <code>Update on reload</code> checked, so every time you make changes to the service worker, it re-installs it automatically.</p>
</blockquote>
<p><img src="https://passos.com.au/content/images/2018/10/service-worker.jpg" alt="Push Notifications + Angular + .NET Core"></p>
<h3 id="3createpublicandprivatekeys">#3 Create Public and Private keys</h3>
<p>Before we get to the next step which, we need a public and private keys so all the communication between the sender and the receiver is encrypted. There are several ways to do it, but the simpler way I found was via the website <a href="https://web-push-codelab.glitch.me">https://web-push-codelab.glitch.me</a>. You can just go there and copy both the <strong>Public key</strong> and <strong>Private key</strong>. We're going to add our <strong>Public key</strong> to the <code>environment.ts</code> file:</p>
<pre><code class="language-javascript">export const environment = {
  production: false,
  applicationServerPublicKey: `[PUBLIC KEY]`
};
</code></pre>
<p>In the server side, we're going to add them to the <code>appsettings.json</code>.</p>
<pre><code class="language-javascript">{
  &quot;VapidDetails&quot;: {
    &quot;Subject&quot;: &quot;[ORIGIN URL, EX:https://localhost:4201]&quot;,
    &quot;PublicKey&quot;: &quot;[PUBLIC KEY]&quot;,
    &quot;PrivateKey&quot;: &quot;[PRIVATE KEY]&quot;
  }
}

</code></pre>
<h3 id="4subscribeandunsubscribe">#4 Subscribe and Unsubscribe</h3>
<p>Instead of asking the user to trust the site as soon as they land on the site, it's a best practice that we ask for permission on demand so the user knows what he's agreeing to. So we're going to create <code>subscribe</code> and <code>unsubscribe</code> methods in the just created service and call this from the UI.</p>
<p>The <code>subcribe</code> method encrypts the public key and uses the <code>pushManager</code> to subscribe the user:</p>
<pre><code class="language-javascript">subscribe() {
  this.pushNotificationStatus.isInProgress = true;
  //check the source code to get the method below
  const applicationServerKey = this.urlB64ToUint8Array(environment.applicationServerPublicKey);
  this.swRegistration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: applicationServerKey
  })
    .then(subscription =&gt; {
      console.log(JSON.parse(JSON.stringify(subscription)));
      this.pushNotificationStatus.isSubscribed = true;
    })
    .catch(err =&gt; {
      console.log('Failed to subscribe the user: ', err);
    })
    .then(() =&gt; {
      this.pushNotificationStatus.isInProgress = false;
    });
}
</code></pre>
<p>I've created a notifications component so I can add a button to call the subscribe method and as soon as I call it, the user will be prompted to either allow or block as per screenshot below. Once they allow, you should have the subscription which will have everything you need to send push notifications.</p>
<blockquote>
<p>You might want to copy the subscription from the logs to test the push notifications later on</p>
</blockquote>
<p><img src="https://passos.com.au/content/images/2018/10/push-request.jpg" alt="Push Notifications + Angular + .NET Core">.</p>
<p>The <code>unsubscribe</code> method will work very similarly:</p>
<pre><code class="language-javascript">unsubscribe() {
  this.pushNotificationStatus.isInProgress = true;
  this.swRegistration.pushManager.getSubscription()
    .then(function (subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .catch(function (error) {
      console.log('Error unsubscribing', error);
    })
    .then(() =&gt; {
      this.pushNotificationStatus.isSubscribed = false;
      this.pushNotificationStatus.isInProgress = false;
    });
}
</code></pre>
<p>In order to make my life easier, I've created a <code>toggleSubscription</code> method, since we already have a flag that recognizes if the user is subscribed or not:</p>
<pre><code class="language-javascript">toggleSubscription() {
  if (this.pushNotificationStatus.isSubscribed) {
    this.unsubscribe();
  } else {
    this.subscribe();
  }
}
</code></pre>
<h3 id="5handlingpushevents">#5 Handling push events</h3>
<p>Now that we have a subscription, it's time to start handling push events. That's where the service worker is going to shine. Let's go now to the <code>sw.js</code> file.</p>
<blockquote>
<p>WARNING: The service worker is javascript file, not typescript.</p>
</blockquote>
<p>We're going to add an event listener for the push event.</p>
<pre><code class="language-javascript">
'use strict';

self.addEventListener('push', function (event) {
    console.log('[Service Worker] Push Received.');
    var data = event.data.json();

    const title = data.Title;
            const options = {
                body: data.Message,
                icon: 'assets/push.png',
                badge: 'assets/push.png',
                data: data.Url
            };

            const promiseChain = self.registration.showNotification(title, options);

    event.waitUntil(promiseChain);
});
</code></pre>
<p>To test it out, let's go back to <a href="https://web-push-codelab.glitch.me">https://web-push-codelab.glitch.me</a> and paste the subscription we got from the previous step. Notice that I'm sending a json document in the message, but you could send just text.<br>
<img src="https://passos.com.au/content/images/2018/10/sending-push-from-web.jpg" alt="Push Notifications + Angular + .NET Core"></p>
<p>You probably noticed also that I added a Url to the json document I sent and I'm adding it to the options of the notification, but at this point in time, if you click on the notification, it's not going to do anything and that's because we have to start listening for another event, the <code>notificationclick</code>. So let's add it:</p>
<pre><code class="language-javascript">self.addEventListener('notificationclick', function (event) {
    const urlToOpen = new URL(event.notification.data, self.location.origin).href;

    event.notification.close();

    event.waitUntil(clients.openWindow(urlToOpen));
});
</code></pre>
<p>Great, now if you click on the notification, it will open the Url we sent.</p>
<p>Okay, but what if I already have a tab open of that site, or if I am on the site itself right now? It's going to be a pain in the bum to start getting all those push notifications and opening always a new instance. There's a solution for that, this <a href="https://web-push-book.gauntface.com/chapter-05/04-common-notification-patterns/">post</a> talks about all different approaches to improve the push experience which I'm implementing in the source code, but I'm not going to cover here as this post is already getting too long.</p>
<h3 id="6handlesubscriptionsintheserver">#6 Handle subscriptions in the server</h3>
<p>For this section, we're going to need both the public and private keys we created in the step #3, also we're going to need to modify the <code>subscribe</code> and <code>unsubscribe</code> methods so it calls the API endpoints to manage the subscriptions.</p>
<blockquote>
<p>For testing purposes, I'm going to save all the subscription into memory, but if you're going to implement this somewhere, be sure to persist the subscriptions somewhere else.</p>
</blockquote>
<p>To send the push notifications, I'm going to use a nuget package called <a href="https://www.nuget.org/packages/WebPush/">WebPush</a>. I've also configured in the startup to inject the <code>VapidDetails</code> which are the details we need to send the push notification: Subject (Origin URL), Public and Private keys.</p>
<p>I've created a <code>Notification</code> controller to handle 3 things: <strong>Subscribe</strong>, <strong>Unsubscribe</strong> and <strong>Broadcast</strong>:</p>
<pre><code class="language-csharp">[ApiController]
[Route(&quot;api/[controller]&quot;)]
public class NotificationController : Controller
{
    public static List&lt;PushSubscription&gt; Subscriptions { get; set; } = new List&lt;PushSubscription&gt;();

    [HttpPost(&quot;subscribe&quot;)]
    [ProducesResponseType((int)HttpStatusCode.OK)]
    public void Subscribe([FromBody] PushSubscription sub)
    {
        Subscriptions.Add(sub);
    }

    [HttpPost(&quot;unsubscribe&quot;)]
    [ProducesResponseType((int)HttpStatusCode.OK)]
    public void Unsubscribe([FromBody] PushSubscription sub)
    {
        var item = Subscriptions.FirstOrDefault(s =&gt; s.Endpoint == sub.Endpoint);
        if (item != null)
        {
            Subscriptions.Remove(item);
        }
    }

    [HttpPost(&quot;broadcast&quot;)]
    [ProducesResponseType((int)HttpStatusCode.OK)]
    public void Broadcast([FromBody] NotificationModel message, [FromServices] VapidDetails vapidDetails)
    {
        var client = new WebPushClient();
        var serializedMessage = JsonConvert.SerializeObject(message);
        foreach (var pushSubscription in Subscriptions)
        {
            client.SendNotification(pushSubscription, serializedMessage, vapidDetails);
        }

    }
}
</code></pre>
<p>Back to the Angular side of the application, I'm going to update the <code>subscribe</code> and <code>unsubscribe</code> methods to call the server side endpoints.</p>
<blockquote>
<p>In order to talk to the server, I've implemented OpenApi with code generation, to know more about it, check this <a href="http://www.codingflow.net/building-single-page-applications-on-asp-net-core-2-1-with-angular-6-part-3-implementing-open-api/">post</a>. My <a href="https://github.com/RSuter/NSwag">NSWAG</a> is also in the source control if you need to re-generate the typescript file.</p>
</blockquote>
<p>So now my <code>subscribe</code> method is calling</p>
<pre><code class="language-javascript">this.notificationService.subscribe(&lt;PushSubscription&gt;{
  auth: newSub.keys.auth,
  p256Dh: newSub.keys.p256dh,
  endPoint: newSub.endpoint
}).subscribe(s =&gt; {
  this.pushNotificationStatus.isSubscribed = true;
})
</code></pre>
<p>And the <code>unsubscribe</code> is calling</p>
<pre><code class="language-javascript">this.notificationService.unsubscribe(&lt;PushSubscription&gt;{
  auth: sub.keys.auth,
  p256Dh: sub.keys.p256dh,
  endPoint: sub.endpoint
}).subscribe(() =&gt; {
  this.pushNotificationStatus.isSubscribed = false;
  this.pushNotificationStatus.isInProgress = false;
});
</code></pre>
<h3 id="7sendpusheventsfromtheserver">#7. Send push events from the server</h3>
<p>Alright, now to test it all out, I've created a broadcast component to call the broadcast endpoint in the notification controller. Let's see it in action.</p>
<ol>
<li>On the right hand side I've got Chrome which I'm going to broadcast the message from, on the left hand side Microsoft Edge.</li>
<li>When I send the message, it broadcasts to everyone who's subscribed.</li>
<li>I get a notification for Edge</li>
<li>I get an in app notification for chrome since I have it focused.</li>
</ol>
<p><img src="https://passos.com.au/content/images/2018/10/push.gif" alt="Push Notifications + Angular + .NET Core"></p>
<p>Hope it helps. Have fun!!!</p>
<p>Cheers.</p>
</div>]]></content:encoded></item><item><title><![CDATA[NDC Sydney 2018]]></title><description><![CDATA[<div class="kg-card-markdown"><p>In the last 2 years, I went to <a href="https://ndcsydney.com/">NDC Sydney</a> and it's always been a great event, possibly the best conference I've been, but this time it would be different, this time I wasn't only attending but also presenting. While I did a hell lot of prep to get there,</p></div>]]></description><link>https://passos.com.au/ndc-sydney-2018/</link><guid isPermaLink="false">5b9b497ae05d93183ce0efe6</guid><category><![CDATA[NDC]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Tue, 25 Sep 2018 03:07:16 GMT</pubDate><media:content url="https://passos.com.au/content/images/2018/09/2018-09-14_15-47-11.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2018/09/2018-09-14_15-47-11.png" alt="NDC Sydney 2018"><p>In the last 2 years, I went to <a href="https://ndcsydney.com/">NDC Sydney</a> and it's always been a great event, possibly the best conference I've been, but this time it would be different, this time I wasn't only attending but also presenting. While I did a hell lot of prep to get there, I was still sh*ting on my pants, but let's see how it went. I'm sure it would be awesome anyway.</p>
<blockquote>
<p>I'll add the recordings as soon as they are available</p>
</blockquote>
<h5 id="preconference">Pre conference</h5>
<p>Let's start with the call for papers: using <a href="https://sessionize.com">Sessionize</a> makes our lives so much easier, I usually have a couple of talks lined up, so the process of submitting talks was a piece of cake. I just wish all other conferences would use it.</p>
<p>Once I was accepted to speak, the whole organization of flights and accommodation was seamless. They got in touch and sorted everything months before the event, giving us enough time to do any preparation from our end.</p>
<h5 id="mygoalthistime">My goal this time</h5>
<p>I was presenting, so my first goal obviously was to do a good job so I could keep coming back, but it wasn't only that. In the previous NDCs, my goal was to watch as many presentations as possible to learn as much as my brain could handle, this time I wasn't going to even try that because I know from previous experience that this is just not possible, I would only have a brain freeze (if there's such thing). This time my main goal was to talk to people, make connections, learn from the great minds. It's one of those things that this conference allows us.</p>
<h5 id="kickoff">Kick off</h5>
<p>As expected, the kick off of the best developer conference in Australia (in my opinion) was amazing, the whole atmosphere was so good. It was great to see so many first time attendees and speakers (including myself), which made me feel even more comfortable.</p>
<h5 id="conference">Conference</h5>
<p>Overall, it was amazing. Great people, great talks, great food, great venue, great organizers and volunteers, great after parties.</p>
<div class="gallery" rows="5">
    <img src="https://passos.com.au/content/images/2018/09/DnXkqB1U4AINpKv.jpg" alt="NDC Sydney 2018">
    <img src="https://passos.com.au/content/images/2018/09/DncoLsGUwAEgTCd.jpg" alt="NDC Sydney 2018">
    <img src="https://passos.com.au/content/images/2018/09/DnhVNKRU0AAZ2aB.jpg" alt="NDC Sydney 2018">
    <img src="https://passos.com.au/content/images/2018/09/DniwO8MU8AQBT2X.jpg" alt="NDC Sydney 2018">
    <img src="https://passos.com.au/content/images/2018/09/Dnk-GVlV4AA-PDw.jpg" alt="NDC Sydney 2018">
</div>
<h5 id="outcome">Outcome</h5>
<p>As I said, the conference was great, but here are some of my high and downlights.</p>
<p>Highlights:</p>
<ul>
<li>Presenting at the conference I love and not getting any reds</li>
<li>Being amongst so many smart people</li>
<li>Interviewing for <a href="https://tv.ssw.com/">SSW TV</a></li>
<li>Staying at <a href="https://www3.hilton.com/en/index.html">Hilton</a></li>
<li>Networking and planning next conferences</li>
<li>Boat cruise</li>
<li>Afterparty</li>
</ul>
<p>Downlights:</p>
<ul>
<li>Not attending <a href="https://pubconf.io/">PubConf</a></li>
</ul>
<p>That's it. I'm looking forward to the next NDC, either <a href="https://ndc-london.com/">NDC London</a>, NDC Portugal, <a href="https://ndcoslo.com/">NDC Oslo</a> or anything that comes first in my way.</p>
<p>Cheers.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Getting Started: EF Core + Cosmos Db]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I was really looking forward to having the <a href="https://azure.microsoft.com/en-gb/services/cosmos-db/">Cosmos Db</a> <a href="https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Cosmos.Sql/">provider for Entity Framework Core</a>. So much that I actually submitted a talk for <a href="https://ndcsydney.com/">NDC Sydney</a> about how to get started with it even before the provider was out, and it's finally out, still in preview so a lot of</p></div>]]></description><link>https://passos.com.au/ef-core-cosmos-db/</link><guid isPermaLink="false">5b8f1655429200163436e328</guid><category><![CDATA[Azure]]></category><category><![CDATA[Getting Started]]></category><category><![CDATA[entity framework core]]></category><category><![CDATA[CosmosDb]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Fri, 07 Sep 2018 02:06:16 GMT</pubDate><media:content url="https://passos.com.au/content/images/2018/09/ef-love-cosmos.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2018/09/ef-love-cosmos.png" alt="Getting Started: EF Core + Cosmos Db"><p>I was really looking forward to having the <a href="https://azure.microsoft.com/en-gb/services/cosmos-db/">Cosmos Db</a> <a href="https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Cosmos.Sql/">provider for Entity Framework Core</a>. So much that I actually submitted a talk for <a href="https://ndcsydney.com/">NDC Sydney</a> about how to get started with it even before the provider was out, and it's finally out, still in preview so a lot of things might change, but I'm very excited all the same.</p>
<blockquote>
<p>As always, the source code covered in this post is available <a href="https://github.com/thiagospassos/ef-core-cosmos-db">here</a></p>
</blockquote>
<p>Let's see how to put it together.</p>
<h3 id="creatinganinstanceofcosmosdbdatabase">Creating an instance of Cosmos Db + Database</h3>
<p>In the <a href="https://portal.azure.com">Azure Portal</a>, you're going to look for Cosmos DB. Just make sure you select the SQL Api when creating it as this is the one supported by EF Core.<br>
<img src="https://passos.com.au/content/images/2018/09/step2.png" alt="Getting Started: EF Core + Cosmos Db"></p>
<p>Once the instance is created is time to create a new database<br>
<img src="https://passos.com.au/content/images/2018/09/step3.png" alt="Getting Started: EF Core + Cosmos Db"></p>
<p>This is the database name we're going to use in our application, so save it.<br>
<img src="https://passos.com.au/content/images/2018/09/step4.png" alt="Getting Started: EF Core + Cosmos Db"></p>
<p>And the collection is like a table in SQL, this is going to line up with a repository (DbSet)<br>
<img src="https://passos.com.au/content/images/2018/09/step5.png" alt="Getting Started: EF Core + Cosmos Db"></p>
<p>Now give a name to your collection, but be very careful with the throughput.<br>
<img src="https://passos.com.au/content/images/2018/09/step6.png" alt="Getting Started: EF Core + Cosmos Db"></p>
<blockquote>
<p><strong>WARNING!!!</strong> You can pay a lot of money if you're careless about the throughput. There's even a check box for you to acknowledge if you set a high value. With the value below, you're looking to pay <strong>$11,520.00/month</strong>. That's crazy!!!<br>
<img src="https://passos.com.au/content/images/2018/09/step7.png" alt="Getting Started: EF Core + Cosmos Db"></p>
</blockquote>
<p>You're all set from here!<br>
<img src="https://passos.com.au/content/images/2018/09/step8.png" alt="Getting Started: EF Core + Cosmos Db"></p>
<p>We just need some keys to configure our application<br>
<img src="https://passos.com.au/content/images/2018/09/step9.png" alt="Getting Started: EF Core + Cosmos Db"></p>
<h3 id="bringingintheprovider">Bringing in the provider</h3>
<p>You can do either via the command line which would be <code>dotnet add package Microsoft.EntityFrameworkCore.Cosmos.Sql --version 2.2.0-preview1-35029</code> or you can use the Visual Studio Package Manager as bellow. There were only <strong>72</strong> downloads at the time. Wow!!!! Is that a good thing (I'm one of the 72 crazy people in the world) or that's a sign I'm going to be in trouble if I try it in production??? :D<br>
<img src="https://passos.com.au/content/images/2018/09/step1.png" alt="Getting Started: EF Core + Cosmos Db"></p>
<h3 id="settingupintheproject">Setting up in the project</h3>
<p>In my application, I've created a <code>appsettings.json</code> so I can bring the configuration in:</p>
<pre><code class="language-json">{
  &quot;CosmosDb&quot;: {
    &quot;EndpointUrl&quot;: &quot;&lt;URI IN THE KEYS SECTION&gt;&quot;,
    &quot;PrivateKey&quot;: &quot;&lt;PRIMARY OR SECONDARY KEY IN THE KEYS SECTION&gt;&quot;,
    &quot;DbName&quot;: &quot;&lt;NAME OF THE DATABASE&gt;&quot;
  }
}
</code></pre>
<p>In my dependency injection configuration method (if you're creating a Web Api or MVC app, that would potentially be in the <code>startup.cs</code>), I'm going to hook things up.</p>
<pre><code class="language-csharp">public static IContainer IocConfig()
{
    ...

    serviceCollection.AddEntityFrameworkCosmosSql();
    serviceCollection.AddDbContext&lt;BlogDbContext&gt;(options =&gt;
    {
        options.UseCosmosSql(
            new Uri(Configuration[&quot;CosmosDb:EndpointUrl&quot;]),
            Configuration[&quot;CosmosDb:PrivateKey&quot;],
            Configuration[&quot;CosmosDb:DbName&quot;]);
    });
    
    ...
}
</code></pre>
<p>My <code>DbContext</code> looks very similar to any other.</p>
<pre><code class="language-csharp">public class BlogDbContext : DbContext
{
    public DbSet&lt;Post&gt; Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity&lt;Post&gt;();
        var blog = modelBuilder.Entity&lt;Post&gt;().Metadata;
        // This needs to line up with the collection previously created
        blog.CosmosSql().CollectionName = nameof(Posts);
    }
}
</code></pre>
<h3 id="letstestride">Let's test ride</h3>
<p>I've created a command (<a href="https://martinfowler.com/bliki/CQRS.html">CQRS</a> pattern) to add a post to the database, but I'm simply calling 2 lines inside it</p>
<pre><code class="language-csharp">await _dbContext.Posts.AddAsync(entity);
await _dbContext.SaveChangesAsync();
</code></pre>
<p>In my console app, I'm building up the entity before sending to the command and I'm printing the generated ID at the end. That's how it looks when it's running:<br>
<img src="https://passos.com.au/content/images/2018/09/step10.png" alt="Getting Started: EF Core + Cosmos Db"></p>
<p>Now we can check in the portal and see if the post was created.<br>
<img src="https://passos.com.au/content/images/2018/09/step11.png" alt="Getting Started: EF Core + Cosmos Db"></p>
<p>And <em>voilà!!!</em></p>
<p>Hope it helps.</p>
<p>Cheers</p>
</div>]]></content:encoded></item><item><title><![CDATA[Azure Functions for the Enterprise + Setting up CI/CD Pipelines]]></title><description><![CDATA[<div class="kg-card-markdown"><p>In the last <a href="https://passos.com.au/enterprise-azure-functions-2">post</a>, we talked about how to keep your API layer as slim as possible and then expose them through <a href="https://twitter.com/AzureFunctions">Azure Functions</a> and I finished the post deploying to <a href="http://portal.azure.com">Azure</a> using the frightful <a href="https://damianbrady.com.au/2018/02/01/friends-dont-let-friends-right-click-publish/">right-click publish</a>. And as promised, this time we're going to go through the process of</p></div>]]></description><link>https://passos.com.au/enterprise-azure-functions-3/</link><guid isPermaLink="false">5b7e6586a2758d11acf81bf6</guid><category><![CDATA[Azure]]></category><category><![CDATA[azure functions]]></category><category><![CDATA[Azure Functions for the Enterprise]]></category><category><![CDATA[VSTS]]></category><category><![CDATA[devops]]></category><category><![CDATA[Continuous Integration]]></category><category><![CDATA[Continuous Delivery]]></category><category><![CDATA[CICD]]></category><dc:creator><![CDATA[Thiago Passos]]></dc:creator><pubDate>Thu, 23 Aug 2018 08:35:35 GMT</pubDate><media:content url="https://passos.com.au/content/images/2018/08/splash.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://passos.com.au/content/images/2018/08/splash.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><p>In the last <a href="https://passos.com.au/enterprise-azure-functions-2">post</a>, we talked about how to keep your API layer as slim as possible and then expose them through <a href="https://twitter.com/AzureFunctions">Azure Functions</a> and I finished the post deploying to <a href="http://portal.azure.com">Azure</a> using the frightful <a href="https://damianbrady.com.au/2018/02/01/friends-dont-let-friends-right-click-publish/">right-click publish</a>. And as promised, this time we're going to go through the process of setting up your CI/CD pipelines.</p>
<blockquote>
<p>The source code covered in this series is in this <a href="https://github.com/thiagospassos/enterprisy-functions">GitHub repository</a>.</p>
</blockquote>
<p>If you find it easier, you can follow the steps in the video below.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/zN0W2IPF1hI" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
<p>We're going to use <a href="https://visualstudio.microsoft.com/">Visual Studio Team Services (VSTS)</a> for the CI/CD pipelines, if you do not have an account yet, I highly recommend, it's free for up to 5 users. Just go to <a href="https://visualstudio.microsoft.com/">visualstudio.com</a> to get started.</p>
<h4 id="prerequisites">Prerequisites</h4>
<ul>
<li>Azure subscription, create a <a href="https://azure.microsoft.com/free/?ref=microsoft.com&amp;utm_source=microsoft.com&amp;utm_medium=docs&amp;utm_campaign=visualstudio">free account</a> before you begin.</li>
<li>VSTS account</li>
<li>Git repository (it can be inside of VSTS or not, up to you)</li>
</ul>
<p>Here are the steps we're going to follow:</p>
<h4 id="1createabuildpipeline">1. Create a build pipeline</h4>
<p>The screenshots are very self explainatory, but I'll comment on some of them.<br>
<img src="https://passos.com.au/content/images/2018/08/step01.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><br>
Even though I'm using <a href="https://github.com/thiagospassos/enterprisy-functions">GitHub</a>, you can chose whatever Git repository you want.<br>
<img src="https://passos.com.au/content/images/2018/08/step02.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><br>
Keep in mind here that I'm using the version 2.0 of Azure Functions which supports .NET Core. If it's not your case, then this step will be different.<br>
<img src="https://passos.com.au/content/images/2018/08/step03.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><br>
The only thing you need to customize in the build is the publish step, because, by default, it has the option <strong>Publish Web Projects</strong> checked which would NOT pick up your Azure Functions project.<br>
<img src="https://passos.com.au/content/images/2018/08/step04.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"></p>
<h4 id="2createareleasepipeline">2. Create a release pipeline</h4>
<p>You're all done with your build pipeline, now it's time to create your release pipeline.<br>
<img src="https://passos.com.au/content/images/2018/08/step05.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><br>
<img src="https://passos.com.au/content/images/2018/08/step06.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><br>
We're going to use as artifact what was generated in the build pipeline.<br>
<img src="https://passos.com.au/content/images/2018/08/step07.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><br>
This step is not necessarly mandatory. You can chose to kick off the release pipeline manually.<br>
<img src="https://passos.com.au/content/images/2018/08/step08.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><br>
In this example, I'm just going over a single environment, but you can add as many environments as you want.<br>
<img src="https://passos.com.au/content/images/2018/08/step09.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><br>
Now you can see the advantage of using VSTS for the CD pipeline. The integration with Azure is amazing. Your list of subscriptions shows up here automatically as long as you're using the same Microsoft account to log in.<br>
<img src="https://passos.com.au/content/images/2018/08/step10.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><br>
And the last thing to do here is to select the right package to deploy, otherwise it's going to deploy all packages generated by the CI pipeline and the deployment will most likely fail.<br>
<img src="https://passos.com.au/content/images/2018/08/step11.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"></p>
<h4 id="extrassettingupdeploymentvariables">Extras: Setting up deployment variables</h4>
<p>Setting up variables is very easy for your deployment enviroments.<br>
<img src="https://passos.com.au/content/images/2018/08/step12.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"><br>
And you can use across all your deployment tasks by just using <code>$([Variable Name])</code>, in my case I'm using <code>$(ConnectionString)</code>.<br>
<img src="https://passos.com.au/content/images/2018/08/step13.png" alt="Azure Functions for the Enterprise + Setting up CI/CD Pipelines"></p>
<p>Hope it helps.</p>
<p>Cheers</p>
</div>]]></content:encoded></item></channel></rss>