{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Articles by Marc Duiker",
  "language": "en",
  "home_page_url": "https://marcduiker.dev/",
  "feed_url": "https://marcduiker.dev/feed.json",
  "description": "I ❤️ OSS, Improving Developer Experience, Drawing Pixel Art, and Handcrafting Generative Art",
  "authors": [
    {
      "name": "Marc Duiker"
    }
  ],
  "items": [{
      "id": "https://marcduiker.dev/articles/managing-the-dapr-project-kiosk-at-kubecon-europe-2026/",
      "url": "https://marcduiker.dev/articles/managing-the-dapr-project-kiosk-at-kubecon-europe-2026/",
      "title": "Managing the Dapr Project Kiosk at KubeCon Europe 2026",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/155.1.kubecon-eu-440w.webp 440w, /assets/images/155.1.kubecon-eu-650w.webp 650w, /assets/images/155.1.kubecon-eu-960w.webp 960w, /assets/images/155.1.kubecon-eu-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/155.1.kubecon-eu-1200w.webp\" width=\"1200\" height=\"800\" alt=\"Dapr project kiosk at KubeCon Europe 2026\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Marc and Oliver at the Dapr project kiosk at KubeCon Europe 2026</figcaption></figure><p>From March 24th to 26th, I was at <a href=\"https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/\" rel=\"noopener\">KubeCon Europe 2026</a> in my role as Dapr Community Manager, managing the Dapr OSS kiosk in the project pavilion alongside <a href=\"https://www.linkedin.com/in/oliver-tomlinson-59ba1365\" rel=\"noopener\">Oliver Tomlinson</a> (Dapr STC member). While I wasn’t speaking at the conference this year, it was still an incredibly busy and rewarding week!</p><p>The Dapr project update video was shown before the keynote sessions, which drove many attendees to our kiosk. It was fantastic to see so much interest in Dapr from the community — we had a steady stream of people stopping by throughout the week to learn more, ask questions, and share how they are using Dapr in their own projects. Oliver did a brilliant job explaining Dapr concepts and answering all kinds of technical questions from the attendees. I’m very grateful he could join me again this year 🙏.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/155.2.kubecon-eu-440w.webp 440w, /assets/images/155.2.kubecon-eu-563w.webp 563w\" sizes=\"90vw\"><img src=\"/assets/images/155.2.kubecon-eu-563w.webp\" width=\"563\" height=\"750\" alt=\"Oliver and Myself at the Dapr project kiosk\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Oliver and Myself at the Dapr project kiosk</figcaption></figure><p>Dapr was clearly a hot topic at the conference this year. <a href=\"https://github.com/dapr/dapr-agents\" rel=\"noopener\">Dapr Agents</a> just went GA and was mentioned in several talks, including a keynote by Fabian Steinbach from ZEISS!</p><p>All the KubeCon Europe 2026 sessions are available on the <a href=\"https://www.youtube.com/playlist?list=PLj6h78yzYM2MXCOWSN9CqqID6OOvF7wxL\" rel=\"noopener\">CNCF YouTube playlist</a>. There are so many great talks in there — well worth a watch!</p><p>Thanks to everyone who stopped by the Dapr kiosk! If you have any questions about Dapr make sure to join the <a href=\"http://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a>. Until next time, KubeCon! 👋</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/155.3.kubecon-eu-422w.webp 422w\" sizes=\"90vw\"><img src=\"/assets/images/155.3.kubecon-eu-422w.webp\" width=\"422\" height=\"750\" alt=\"Dapr stickers\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Dapr stickers</figcaption></figure><p><em>I wasn’t in full health when KubeCon started and being there every day talking to dozens and dozens of people was not beneficial for my health either 😬. On the Friday after KubeCon, I went to my physician and heard I got a sinus infection and a starting double pneumonia! 😱 Since then I finished my antibiotics treatment and I’m feeling much better after taking plenty of rest. I hope I’ll be in a better shape at the next KubeCon! 😅</em></p>",
      "date_published": "2026-04-06T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-futuretech-2026/",
      "url": "https://marcduiker.dev/articles/speaking-at-futuretech-2026/",
      "title": "Speaking at FutureTech 2026",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/154.1.futuretech-440w.webp 440w, /assets/images/154.1.futuretech-650w.webp 650w, /assets/images/154.1.futuretech-960w.webp 960w, /assets/images/154.1.futuretech-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/154.1.futuretech-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Speaking at FutureTech 2026\" loading=\"960\" decoding=\"async\"></picture><figcaption>Speaking at FutureTech 2026</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-workflow-concerto\" target=\"_blank\">Slides &amp; code demos on GitHub</a></p><hr><p>On March 11, I was at <a href=\"https://futuretech.nl/\" rel=\"noopener\">FutureTech</a> to give my session, <em>‘Turning microservice chaos into a beautiful concerto’</em>.</p><p>Building reliable distributed applications can feel like conducting a sloppy orchestra, where musicians play too loud or can’t keep the tempo. As the conductor, you need to set the pace and give instructions to deliver a beautiful performance. When you build microservices and need to coordinate multistep processes, you have to think about the order of execution, time-outs, cascading failures, inconsistent state, and you have very little control to manage the process once it’s started.</p><p>Dapr Workflow provides a solution through durable execution, a programming model that treats distributed processes like a musical score. Just as a conductor coordinates multiple musicians to play a symphony, Dapr Workflow orchestrates microservices with built-in state persistence, workflow patterns to control the processes in great detail, and workflow management APIs to operate workflows reliably.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/154.2.futuretech-422w.webp 422w\" sizes=\"90vw\"><img src=\"/assets/images/154.2.futuretech-422w.webp\" width=\"422\" height=\"750\" alt=\"My setup at FutureTech 2026\" loading=\"420\" decoding=\"async\"></picture><figcaption>My setup at FutureTech 2026</figcaption></figure><p>In my session I ran several Dapr workflows, written in .NET, to sequence music notes, where each workflow step communicates with a microservice that sends a note event to a front-end that uses Web MIDI to play my Behringer Grind synthesizer in real-time. The audience could listen how workflows were progressing! This was the first time I presented this session and performed with one of my synthesizers, and I absolutely loved it. I’m looking forward to give this session more frequently!</p><p>A big thank you to the FutureTech organizers and sponsors for making this event happen, and thanks to the attendees for attending my session and the enthusiastic response!</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2026-03-11T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-container-days-london-2026/",
      "url": "https://marcduiker.dev/articles/speaking-at-container-days-london-2026/",
      "title": "Speaking at Container Days London 2026",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/153.1.containerdays-london-440w.webp 440w, /assets/images/153.1.containerdays-london-650w.webp 650w, /assets/images/153.1.containerdays-london-960w.webp 960w, /assets/images/153.1.containerdays-london-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/153.1.containerdays-london-1200w.webp\" width=\"1200\" height=\"637\" alt=\"Speaking at Container Days London 2026\" loading=\"960\" decoding=\"async\"></picture><figcaption>Speaking at Container Days London 2026 (photo by Mauricio 'Salaboy' Salatino )</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-building-block-apis\" target=\"_blank\">Slides &amp; code demos on GitHub</a></p><hr><p>On February 11 and 12, I was at <a href=\"https://www.containerdays.io/containerdays-london-2026/\" rel=\"noopener\">Container Days London</a> to give my session, <em>‘Decoupling your application code from your infrastructure with Dapr’</em>.</p><p>This was the first time Container Days was held in London, and I was positively surprised by the good turnout. Cloud-native tech is definitely top of mind in the UK.</p><p>I updated my session to include Dapr Workflow, which is becoming more and more popular, and this allowed me to demo the <a href=\"https://www.diagrid.io/blog/improving-the-local-dapr-workflow-experience-diagrid-dashboard\" rel=\"noopener\">Diagrid Dev Dashboard</a> to show how workflow state is stored and that the workflow is automatically resumed after an application crash and restart.</p><p>This is the <a href=\"https://github.com/diagrid-labs/dapr-building-block-apis\" rel=\"noopener\">repo with the demos</a>. It comes with a devcontainer configuration so you can run it yourself easily, either on GitHub Codespaces or locally. And you can use DemoTime to run through the slides and all the demos.</p><p>A big thank you to the Container Days organizers and sponsors for making this event happen, and thanks to the attendees for attending my session and providing me with positive feedback!</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/153.2.containerdays-london-330w.webp 330w\" sizes=\"90vw\"><img src=\"/assets/images/153.2.containerdays-london-330w.webp\" width=\"330\" height=\"613\" alt=\"Session Feedback captured by EngageTime\" loading=\"440\" decoding=\"async\"></picture><figcaption>Session Feedback captured by EngageTime</figcaption></figure><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2026-02-11T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-ndc-london-2026/",
      "url": "https://marcduiker.dev/articles/speaking-at-ndc-london-2026/",
      "title": "Speaking at NDC London 2026",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/152.1.ndc-london-440w.webp 440w, /assets/images/152.1.ndc-london-650w.webp 650w, /assets/images/152.1.ndc-london-960w.webp 960w, /assets/images/152.1.ndc-london-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/152.1.ndc-london-1200w.webp\" width=\"1200\" height=\"554\" alt=\"Speaking at NDC London 2026\" loading=\"960\" decoding=\"async\"></picture><figcaption>Speaking at NDC London 2026 (photo by Alex Thissen)</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-reliable-agentic-systems\" target=\"_blank\">Slides &amp; code demos on GitHub</a></p><hr><p>On January 28-30, I was at <a href=\"https://ndclondon.com/\" rel=\"noopener\">NDC London</a> to give my session, <em>‘Reliable Agentic Systems Require Durable Execution’</em>.</p><p>The NDC conferences are known for their solid speaker lineup and taking great care of their speakers 🙏. I consider myself very fortunate to have been part of their events once again.</p><p>My session is based on this blog post, <a href=\"https://www.anthropic.com/engineering/building-effective-agents\" rel=\"noopener\">Building Effective Agents</a> by the Anthropic Engineering team, and covers different agentic patterns that range from very deterministic to more probabilistic. The implementation in the demo apps is done with Dapr Workflow, the Dapr Conversation API, and local models run with Ollama.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/152.2.ndc-london-440w.webp 440w, /assets/images/152.2.ndc-london-650w.webp 650w, /assets/images/152.2.ndc-london-960w.webp 960w, /assets/images/152.2.ndc-london-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/152.2.ndc-london-1200w.webp\" width=\"1200\" height=\"634\" alt=\"Reliable Agentic Systems Require Durable Execution\" loading=\"960\" decoding=\"async\"></picture><figcaption>Reliable Agentic Systems Require Durable Execution</figcaption></figure><p>This is the <a href=\"https://github.com/diagrid-labs/dapr-reliable-agentic-systems\" rel=\"noopener\">repo with the demos</a>. It comes with a devcontainer configuration so you can run it yourself easily, either on GitHub Codespaces or locally. And you can use DemoTime to run through the slides and all the demos.</p><p>I really enjoyed catching up with other members of the community. It was great meeting Andrew Poole for the first time. He also did his session with DemoTime and was using animated, hand-drawn diagrams, very impressive 🤯. I had so much fun during Davy Davidse’s session about mazes and how to solve them. Love his dry sense of humour 🤣. David Whitney’s session, <em>‘Meditations on Code as Art’</em>, was really inspiring. That was truly a session close to my heart as I really enjoy creating art with code myself. And yes, I think code itself can also be seen as art. Lastly, it was great to see some creative coding at the NDC party where Alan Smith was coding/DJ-ing in <a href=\"https://sonic-pi.net/\" rel=\"noopener\">Sonic Pi</a>. Maybe I can do some creative coding with P5JS at the next NDC? 🤔</p><p>A big thank you to all the NDC organizers and sponsors for making this event happen, and thanks to the attendees for attending my session and giving me very positive feedback (84% green 🟩)!</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2026-01-30T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-heapcon-2025/",
      "url": "https://marcduiker.dev/articles/speaking-at-heapcon-2025/",
      "title": "Speaking at HeapCon 2025",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/151.1.heapcon-440w.webp 440w, /assets/images/151.1.heapcon-650w.webp 650w, /assets/images/151.1.heapcon-960w.webp 960w, /assets/images/151.1.heapcon-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/151.1.heapcon-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Speaking at HeapCon 2025\" loading=\"440\" decoding=\"async\"></picture><figcaption>Speaking at HeapCon 2025</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-building-block-apis\" target=\"_blank\">Slides &amp; code demos on GitHub</a></p><hr><p>On October 30 &amp; 31, I was invited to <a href=\"https://heapcon.io/2025\" rel=\"noopener\">Heapcon</a> in Belgrade Serbia to give my session, <em>‘Start building distributed applications with ease using building block APIs’</em>. It was my first time at this community-driven conference, and it was a really nice experience. It was clear that the organizers have been doing this for several years now, as the entire event ran super smoothly. Thank you, Iva Jovanović and Viktor Bajraktar, for making me feel so welcome!</p><p>At the conference I met two other Dutch speakers: Niek Palm, who gave a session about GitHub Actions security, and Niek van de Ven, who gave a very engaging session about public speaking.</p><p>One of the highlights outside the conference was a dinner with some of the speakers and organizers at a brewery called Docker. Thanks for the great company and conversations!</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/151.2.heapcon-394w.webp 394w\" sizes=\"90vw\"><img src=\"/assets/images/151.2.heapcon-394w.webp\" width=\"394\" height=\"700\" alt=\"Dinner and drinks at Docker\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Dinner and drinks at Docker</figcaption></figure><p>This is the <a href=\"https://github.com/diagrid-labs/dapr-building-block-apis\" rel=\"noopener\">repo with the demos</a>. It comes with a devcontainer configuration so you can run it yourself easily, either on GitHub Codespaces or locally. And you can use Demo Time to run through the slides and all the demos.</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2025-10-31T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-the-net-copenhagen-meetup-2025/",
      "url": "https://marcduiker.dev/articles/speaking-at-the-net-copenhagen-meetup-2025/",
      "title": "Speaking at the .NET Copenhagen Meetup 2025",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/149.1.net-meetup-copenhagen-440w.webp 440w, /assets/images/149.1.net-meetup-copenhagen-650w.webp 650w, /assets/images/149.1.net-meetup-copenhagen-960w.webp 960w, /assets/images/149.1.net-meetup-copenhagen-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/149.1.net-meetup-copenhagen-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Speaking at the .NET Copenhagen meetup\" loading=\"440\" decoding=\"async\"></picture><figcaption>Speaking at the .NET Copenhagen meetup</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" target=\"_blank\">Slides &amp; code demos on GitHub</a></p><hr><p>On September 29, I was at the <a href=\"https://ndccopenhagen.com/\" rel=\"noopener\">.NET Copenhagen Meetup</a> in Denmark to give my session, <em>‘Failure is not an option: Durable Execution + Dapr = 🚀’</em>. This was a Dapr-focussed meetup and was the result of a collaboration between Mauricio Salatino, Alejandro Montenegro, Morten Christensen and myself.</p><p>Mauricio started off with a session about “Cloud Native Developer Experience”. Then I presented my session about Dapr Workflow and resiliency. And finally, Morten closed the event with a session about how Novo Nordisk is using Dapr\".</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/149.2.net-meetup-copenhagen-440w.webp 440w, /assets/images/149.2.net-meetup-copenhagen-650w.webp 650w, /assets/images/149.2.net-meetup-copenhagen-960w.webp 960w, /assets/images/149.2.net-meetup-copenhagen-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/149.2.net-meetup-copenhagen-1200w.webp\" width=\"1200\" height=\"682\" alt=\"Morten, Jonah, me, and Mauricio at the .NET Meetup Copenhagen\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Morten, Jonah, me, and Mauricio at the .NET Meetup Copenhagen</figcaption></figure><p>For my session, I used <a href=\"https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-demo-time\" rel=\"noopener\">DemoTime</a>, a VSCode extension created by <a href=\"https://bsky.app/profile/eliostruyf.com\" rel=\"noopener\">Elio Struyf</a>, to navigate through my slides (Markdown) and code, and also execute the <code>dapr run</code> commands for running my Dapr demos. It worked great and ensured I filled my 60-minute slot perfectly. This time, I also included <a href=\"https://engagetime.live/\" rel=\"noopener\">EngageTime</a> to interact with the audience using polls and share session resources with them.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/149.3.net-meetup-copenhagen-440w.webp 440w, /assets/images/149.3.net-meetup-copenhagen-650w.webp 650w, /assets/images/149.3.net-meetup-copenhagen-960w.webp 960w, /assets/images/149.3.net-meetup-copenhagen-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/149.3.net-meetup-copenhagen-1200w.webp\" width=\"1200\" height=\"233\" alt=\"EngageTime poll: How familiar are you with Dapr?\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>EngageTime poll: How familiar are you with Dapr?</figcaption></figure><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/149.4.net-meetup-copenhagen-440w.webp 440w, /assets/images/149.4.net-meetup-copenhagen-650w.webp 650w, /assets/images/149.4.net-meetup-copenhagen-960w.webp 960w, /assets/images/149.4.net-meetup-copenhagen-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/149.4.net-meetup-copenhagen-1200w.webp\" width=\"1200\" height=\"338\" alt=\"EngageTime poll: Have you used a workflow engine?\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>EngageTime poll: Have you used a workflow engine?</figcaption></figure><p>This is the <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" rel=\"noopener\">repo with the demos</a>. It comes with a devcontainer configuration so you can run it yourself easily, either on GitHub Codespaces or locally. And you can use Demo Time to run through the slides and all the demos.</p><p>It was great seeing Jonah Andersson again! She’s a longtime developer community enthusiast and very active in the Azure scene. Thank you for coming over all the way from Sweden to say hi!</p><p>A big thank you to Alejandro &amp; Google for hosting and Morten for organizing the .NET Copenhagen meetup.</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2025-09-29T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/giving-a-workshop-at-pydata-amsterdam-2025/",
      "url": "https://marcduiker.dev/articles/giving-a-workshop-at-pydata-amsterdam-2025/",
      "title": "Giving a workshop at PyData Amsterdam 2025",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/148.1.pydata-440w.webp 440w, /assets/images/148.1.pydata-650w.webp 650w, /assets/images/148.1.pydata-960w.webp 960w, /assets/images/148.1.pydata-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/148.1.pydata-1200w.webp\" width=\"1200\" height=\"676\" alt=\"Dana presenting at the PyData Amsterdam workshop\" loading=\"440\" decoding=\"async\"></picture><figcaption>Dana presenting at the PyData Amsterdam workshop</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://www.diagrid.io/dapr-university#dapr-agents\" target=\"_blank\">Learn Dapr Agents via Dapr University</a></p><hr><p>On September 24, I was at <a href=\"https://amsterdam.pydata.org/\" rel=\"noopener\">PyData Amsterdam</a> with Dana Arsovska to give a Dapr Agents workshop.</p><p>Dana did a great job making a selection of <a href=\"https://github.com/dapr/dapr-agents/blob/main/quickstarts/README.md\" rel=\"noopener\">Dapr Agents quickstarts</a>, changing it to use the HuggingFace client, and preparing the slide deck.</p><p>I was positively surprised to see so many people attend our workshop! Dana and I only provisioned 15 API keys, but we had about 50 attendees 😅. Due to the large group size, we ran into some rate-limiting issues when initializing the Dapr CLI (which starts pulling container images from the Docker registry). Luckily we could use the Dapr Agents track on the Instruqt platform that uses a cloud sandbox that is fully pre-configured (although the content was slightly different compared to what Dana had prepared). It was a good learning experience to be better prepared next time for a larger group (and completely switch to Instruqt for everyone).</p><p>Many thanks to the PyData program committee and organizers for giving us the opportunity to do this workshop!</p><p>Want to learn more about how to build AI applications with Dapr? Check out the <a href=\"https://www.diagrid.io/dapr-university#dapr-agents\" rel=\"noopener\">Dapr Agents track</a> at Dapr Univerisity.</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2025-09-24T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-ndc-copenhagen-2025/",
      "url": "https://marcduiker.dev/articles/speaking-at-ndc-copenhagen-2025/",
      "title": "Speaking at NDC Copenhagen 2025",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/147.1.ndccopenhagen-440w.webp 440w, /assets/images/147.1.ndccopenhagen-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/147.1.ndccopenhagen-650w.webp\" width=\"650\" height=\"866\" alt=\"NDC Arriving at Copenhagen 2025\" loading=\"440\" decoding=\"async\"></picture><figcaption>Arriving at NDC Copenhagen 2025</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-pub-sub-deep-dive\" target=\"_blank\">Slides &amp; code demos on GitHub</a></p><hr><p>On September 10-11, I was at <a href=\"https://ndccopenhagen.com/\" rel=\"noopener\">NDC Copenhagen</a> in Denmark to give my session <em>‘Async messaging deep dive with Dapr, take your pub/sub skills to the next level 📨🚀’</em>.</p><p>The NDC conferences are known for their solid and diverse speaker lineup. They also take great care of their speakers 🙏. I consider myself very fortunate to have been part of this event again. This was my second time at the Copenhagen edition. <a href=\"../speaking-at-cphdevfest-2024\">Last year</a> the event was named Copenhagen Developer Festival; this year it was rebranded to NDC Copenhagen (similar to the other NDC conferences). I have to say I did miss the festival elements from last year, which then included Matt Parker (the stand-up maths comedian), live-coding music sessions, and demo scene talks. Maybe more artsy elements can be included again next year? I did appreciate the live music performances using a modular synthesizer and a theremin during the breaks.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/147.2.ndccopenhagen-440w.webp 440w, /assets/images/147.2.ndccopenhagen-650w.webp 650w, /assets/images/147.2.ndccopenhagen-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/147.2.ndccopenhagen-960w.webp\" width=\"960\" height=\"720\" alt=\"NDC Speaking at Copenhagen 2025\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Speaking at NDC Copenhagen 2025 (Thanks to Anjuli for taking this picture!)</figcaption></figure><p>For my session, I used <a href=\"https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-demo-time\" rel=\"noopener\">DemoTime</a>, a VSCode extension created by <a href=\"https://bsky.app/profile/eliostruyf.com\" rel=\"noopener\">Elio Struyf</a>, to navigate through my slides (Markdown) and code, and also execute the <code>dapr run</code> commands for running my Dapr demos. It worked great and ensured I filled my 60-minute slot perfectly. This time, I also included <a href=\"https://engagetime.live/\" rel=\"noopener\">EngageTime</a> to interact with the audience using polls and share session resources with them.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/147.4.ndccopenhagen-440w.webp 440w, /assets/images/147.4.ndccopenhagen-650w.webp 650w, /assets/images/147.4.ndccopenhagen-960w.webp 960w, /assets/images/147.4.ndccopenhagen-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/147.4.ndccopenhagen-1200w.webp\" width=\"1200\" height=\"620\" alt=\"EngageTime poll: How familiar are you with Dapr?\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>EngageTime poll: How familiar are you with Dapr?</figcaption></figure><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/147.5.ndccopenhagen-440w.webp 440w, /assets/images/147.5.ndccopenhagen-650w.webp 650w, /assets/images/147.5.ndccopenhagen-960w.webp 960w, /assets/images/147.5.ndccopenhagen-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/147.5.ndccopenhagen-1200w.webp\" width=\"1200\" height=\"467\" alt=\"EngageTime poll: Who owns Dapr?\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>EngageTime poll: Who owns Dapr?</figcaption></figure><p>This is the <a href=\"https://github.com/diagrid-labs/dapr-pub-sub-deep-dive\" rel=\"noopener\">repo with the demos</a>. It comes with a devcontainer configuration so you can run it yourself easily, either on GitHub Codespaces or locally. And you can use DemoTime to run through the slides and all the demos.</p><p>As usual, I had loads of stickers to share. The pixel art ones with the retro desktop computer are new this year!</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/147.3.ndccopenhagen-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/147.3.ndccopenhagen-440w.webp\" width=\"440\" height=\"781\" alt=\"Some of my stickers at Copenhagen 2025\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>My stickers 🤩</figcaption></figure><p>I really enjoyed catching up with other members of the community. I met some lovely new (for me) ‘speaker colleagues’ as well 🤗.</p><p>A big thank you to all the NDC organizers and sponsors for organizing this great developer event and for inviting me to talk about Dapr. And thanks to the attendees for joining my session and giving me feedback! I’m happy with my rating of 23 green and 7 yellow cards 😊.</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2025-09-12T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/supercharge-your-technical-presentations-with-demotime/",
      "url": "https://marcduiker.dev/articles/supercharge-your-technical-presentations-with-demotime/",
      "title": "Supercharge your technical presentations with DemoTime",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/146.1.demotime-440w.webp 440w, /assets/images/146.1.demotime-650w.webp 650w, /assets/images/146.1.demotime-960w.webp 960w, /assets/images/146.1.demotime-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/146.1.demotime-1200w.webp\" width=\"1200\" height=\"660\" alt=\"My Dapr 101 presentation using DemoTime\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>My Dapr 101 presentation using DemoTime</figcaption></figure><p>I’ve been doing technical presentations at conferences for over a decade now. I love doing these talks and improving my skills to deliver better presentations and demos.</p><h2 id=\"choosing-the-presentation-tools\"><a href=\"#choosing-the-presentation-tools\" class=\"heading-anchor\">Choosing the presentation tools</a></h2><p>When it comes to the tools and format of my slides I started with <a href=\"https://revealjs.com/\" rel=\"noopener\">reveal.js</a> many years ago to have everything based on HTML and JavaScript for optimal portability on the web. A few years later I switched to hand-drawn slides using PowerPoint (drawn on my Surface laptop). Everyone loved the hand-drawn style, but it was very time-consuming to create them. The slides were also not very accessible since they were mostly exported as images, so I eventually stopped creating them. Regardless of the exact slide format, there was always a gap between showing the slides and running the demos, which I didn’t like.</p><h2 id=\"ide-first-approach\"><a href=\"#ide-first-approach\" class=\"heading-anchor\">IDE-first approach</a></h2><p>Since all of my presentations involve demos, I was looking for a way to close the gap between showing slides, showing code, and running demos. Ideally I wanted to run the entire presentation inside an IDE. I’m a big fan of VSCode due to its extensibility and the fact that it runs on all platforms. So I looked into extensions that would allow me to create a presentation-like experience in VSCode.</p><p>The first solution I came across was <a href=\"https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour\" rel=\"noopener\">CodeTour</a>, a VSCode extension by Jonathan Carter that allows the creation of guided tours through a codebase in a git repo. The extension also supported running terminal commands, which was great for starting my demo applications. I used CodeTour for several years, and it worked well, although it had some limitations for the slides. Eventually the development of CodeTour stopped while VSCode continued to evolve, and my CodeTours were not working any longer 😢.</p><h2 id=\"new-kid-on-the-block-demotime\"><a href=\"#new-kid-on-the-block-demotime\" class=\"heading-anchor\">New kid on the block: DemoTime</a></h2><p>When I was looking for a CodeTour replacement, a fellow MVP and technical presenter, Elio Struyf, told me about a new VSCode extension he was working on called <a href=\"https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-demotime-theme\" rel=\"noopener\">DemoTime</a>. This extension was exactly what I was looking for! It enabled me to seamlessly mix markdown-based slides, show code, and run demos all from within VSCode. DemoTime is really powerful, and I typically describe it as a VSCode automation tool for technical presentations. Everything you can do using the VSCode command palette and the terminal, you can use in DemoTime to create stress-free technical presentations.</p><h2 id=\"how-i-use-demotime\"><a href=\"#how-i-use-demotime\" class=\"heading-anchor\">How I use DemoTime</a></h2><p>My presentations typically follow this structure:</p><ol class=\"list\"><li><strong>Introduction</strong>: I start with a few slides to introduce the topic and myself.</li><li><strong>Show code</strong>: I show the code that I will be running in the demo and explain how it works.</li><li><strong>Run Demos</strong>: I run the demos, which are usually a web services, and I make API calls to them using the REST Client extension for VSCode, and point out log statements the services produce.</li><li><strong>Conclusion</strong>: I wrap up with a few slides to summarize the key points and provide additional resources and a call to action.</li></ol><p>DemoTime allows me to do all of this from within VSCode and every step in the presentation is scripted, which helps me to stay focused and not forget any important points or actions.</p><p>The structure of a DemoTime presentation is captured in a json or yaml file stored in the <code>.demo</code> folder in the root of a git repo.</p><p>Here’s a snippet of a DemoTime presentation for one of my presentations:</p><pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n  <span class=\"token property\">\"$schema\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"https://demotime.elio.dev/demo-time.schema.json\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"title\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Dapr APIs\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"description\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"This presentation covers three Dapr APIs: State Management, Service Invocation, and Pub/Sub.\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"version\"</span><span class=\"token operator\">:</span> <span class=\"token number\">2</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"demos\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">\"title\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Intro\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"description\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"icons\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"start\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"vm\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"end\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"pass-filled\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"steps\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n        <span class=\"token punctuation\">{</span>\n          <span class=\"token property\">\"action\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"executeVSCodeCommand\"</span><span class=\"token punctuation\">,</span>\n          <span class=\"token property\">\"command\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"workbench.action.terminal.killAll\"</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">{</span>\n          <span class=\"token property\">\"action\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"executeVSCodeCommand\"</span><span class=\"token punctuation\">,</span>\n          <span class=\"token property\">\"command\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"workbench.action.closeSidebar\"</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">{</span>\n          <span class=\"token property\">\"action\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"openSlide\"</span><span class=\"token punctuation\">,</span>\n          <span class=\"token property\">\"path\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"/.demo/slides/intro.md\"</span>\n        <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">]</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    ...</code></pre><p>There’s an array of demo objects in the <code>demos</code> property that compose the presentation. Each demo contains a <code>steps</code> array that can include one or more steps. In the above example, the first demo is called “Intro” and it has three steps:</p><ol class=\"list\"><li>Kill (and hide) all running VSCode terminals.</li><li>Close the VSCode sidebar.</li><li>Open the slide at <code>/.demo/slides/intro.md</code>.</li></ol><p>I’m mostly using these five action types in my DemoTime presentations:</p><ul class=\"list\"><li><code>openSlide</code>: Opens a markdown file as a slide.</li><li><code>markdownPreview</code>: Opens a markdown file in the VSCode preview pane, which is great for showing a longer markdown document that does not fit the 16:9 slide ratio (e.g. a GitHub README).</li><li><code>highlight</code>: Opens a file in the editor, and highlights the specified row(s).</li><li><code>executeVSCodeCommand</code>: Executes a VSCode command, I typically use these to change the VSCode UI like showing or hiding panels, terminals, etc.</li><li><code>executeTerminalCommand</code>: Executes a command in the VSCode terminal, I use this to run demo applications.</li></ul><p>There are many more actions available in DemoTime, check out the <a href=\"https://demotime.elio.dev/actions/\" rel=\"noopener\">DemoTime documentation</a> for more details.</p><p>Here’s an example of a DemoTime step that runs a terminal command to start Dapr applications using the Dapr CLI:</p><pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n      <span class=\"token property\">\"title\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Demo 1a: Run\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"icons\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"start\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"terminal-cmd\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"end\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"pass-filled\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"steps\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n        <span class=\"token punctuation\">{</span>\n          <span class=\"token property\">\"action\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"executeTerminalCommand\"</span><span class=\"token punctuation\">,</span>\n          <span class=\"token property\">\"command\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"dapr run -f .\"</span><span class=\"token punctuation\">,</span>\n          <span class=\"token property\">\"terminalId\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"dapr-run\"</span>\n        <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">]</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></code></pre><p>Each time I run Dapr applications, I also want to stop running them once I’ve completed the demo. This is done by typing <code>Ctrl+C</code> in the terminal when using the Dapr CLI directly. Since I don’t want to type anything manually in the terminal during my demos, I had to figure out how to do this in DemoTime by using control characters in the command string. Here’s how I achieved that:</p><pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n      <span class=\"token property\">\"title\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Stop the apps 1\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"icons\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"start\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"terminal-cmd\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"end\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"pass-filled\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"steps\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n        <span class=\"token punctuation\">{</span>\n          <span class=\"token property\">\"action\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"executeVSCodeCommand\"</span><span class=\"token punctuation\">,</span>\n          <span class=\"token property\">\"command\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"workbench.action.terminal.sendSequence\"</span><span class=\"token punctuation\">,</span>\n          <span class=\"token property\">\"terminalId\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"dapr-run\"</span><span class=\"token punctuation\">,</span>\n          <span class=\"token property\">\"args\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token property\">\"text\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\\u0003\"</span>\n          <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">]</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></code></pre><p>The <code>\\u0003</code> command is the control character for <em>end of text</em>, which stops the running Dapr applications in the terminal.</p><p>If you have a long presentation with many demos, the json file can get quite large. I recommend splitting the presentation into multiple DemoTime files. DemoTime will detect all json files in the .demo folder of the git repo and show all the demo steps in the DemoTime panel. This way, you can keep your presentation organized and manageable.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/146.2.demotime-440w.webp 440w, /assets/images/146.2.demotime-650w.webp 650w, /assets/images/146.2.demotime-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/146.2.demotime-960w.webp\" width=\"960\" height=\"358\" alt=\"Using multiple DemoTime json files for a large presentation\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Using multiple DemoTime json files for a large presentation</figcaption></figure><p>If editing json or yaml files manually is not your cup of tea, DemoTime now includes a visual editor that makes it much easier to get up and running with creating your first DemoTime presentation 🤩.</p><h2 id=\"my-impression-and-tips\"><a href=\"#my-impression-and-tips\" class=\"heading-anchor\">My impression and tips</a></h2><p>I’ve given presentations using DemoTime at several conferences now and it worked flawlessly each time! 🚀</p><p>Here are the links to GitHub repos that contain my DemoTime-based presentations:</p><ul class=\"list\"><li><a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" rel=\"noopener\">Dapr Resiliency and Durable Execution</a></li><li><a href=\"https://github.com/diagrid-labs/dapr-building-block-apis\" rel=\"noopener\">Dapr Building Block APIs</a></li><li><a href=\"https://github.com/diagrid-labs/dapr-pub-sub-deep-dive\" rel=\"noopener\">Dapr Pub/Sub Deepdive (work in progress)</a></li></ul><p>I really like that the presentation content is part of the GitHub repo where the demos are located. I usually add a devcontainer configuration to my repos, so people can easily run everything, incl the DemoTime-based presentation, in a GitHub Codespace or locally in a container.</p><p>Here are some tips for using DemoTime:</p><ol class=\"list\"><li><p>I definitely recommend doing a couple of practice runs (this should be standard procedure when giving presentations anyway), especially if you include actions that automate VSCode or execute terminal commands.</p></li><li><p>Ensure your DemoTime presentation is robust and works as expected. What happens if you move back to a previous demo step and move forward again?</p></li><li><p>If you use DemoTime to create, rename, or edit files, make sure you get back in a clean state before you run the presentation again. You can either do a git reset or include a DemoTime step at the end to undo the changes you made.</p></li></ol><p>Elio is continuously improving the extension and is always happy to receive feedback and take action on it. <a href=\"https://demotime.elio.dev/\" rel=\"noopener\">Give DemoTime a try</a> and let Elio know what you think! And if you do like DemoTime, show some support and sponsor him on <a href=\"https://github.com/sponsors/estruyf\" rel=\"noopener\">GitHub</a>.</p>",
      "date_published": "2025-08-05T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/giving-a-dapr-agents-workshop-at-pyladies-amsterdam/",
      "url": "https://marcduiker.dev/articles/giving-a-dapr-agents-workshop-at-pyladies-amsterdam/",
      "title": "Giving a Dapr Agents workshop at Pyladies Amsterdam",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/145.1.pyladies-440w.webp 440w, /assets/images/145.1.pyladies-650w.webp 650w, /assets/images/145.1.pyladies-960w.webp 960w, /assets/images/145.1.pyladies-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/145.1.pyladies-1200w.webp\" width=\"1200\" height=\"731\" alt=\"Dana presenting about Dapr Agents\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Dana presenting about Dapr Agents</figcaption></figure><p>On May 27th, Dana Arsovska and I gave a Dapr Agents workshop for the <a href=\"https://www.meetup.com/pyladiesams/events/307652222/?eventOrigin=group_events_list\" rel=\"noopener\">PyLadies Amsterdam community</a>. Full credits go to Dana since she created the hands-on labs based on the Dapr Agents <a href=\"https://github.com/dapr/dapr-agents/blob/main/quickstarts/README.md\" rel=\"noopener\">quickstarts</a> and took the lead during this workshop. You did great!</p><p>The event was generously hosted by <a href=\"https://jetbrains.com/\" rel=\"noopener\">JetBrains</a>. They have a wonderful office in the center of Amsterdam which was very suitable for our workshop. Thank you Lena for hosting and providing us with delicious pizza!</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/145.2.pyladies-440w.webp 440w, /assets/images/145.2.pyladies-650w.webp 650w, /assets/images/145.2.pyladies-960w.webp 960w, /assets/images/145.2.pyladies-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/145.2.pyladies-1200w.webp\" width=\"1200\" height=\"674\" alt=\"Dapr Agents workshop for Pyladies Amsterdam\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Dapr Agents workshop for Pyladies Amsterdam</figcaption></figure><p>Running this workshop was a excellent opportunity for me to network with Python community members. It was great meeting the Pyladies organizers, Una and Giulia (thank you for your help during the workshop!), and I got introduced to Kally, one of the PyData Amsterdam organizers.</p><p>If you’re interesting in trying Dapr Agents yourself, you can either run the hands-on labs <a href=\"https://github.com/pyladiesams/dapr-in-action-may2025\" rel=\"noopener\">in this repo</a> or try the <a href=\"https://github.com/dapr/dapr-agents/blob/main/quickstarts/README.md\" rel=\"noopener\">Dapr Agents quickstarts</a>.</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2025-05-27T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-kubecon-europe-2025/",
      "url": "https://marcduiker.dev/articles/speaking-at-kubecon-europe-2025/",
      "title": "Speaking at KubeCon Europe 2025",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/144.1.kubecon-440w.webp 440w, /assets/images/144.1.kubecon-650w.webp 650w, /assets/images/144.1.kubecon-960w.webp 960w, /assets/images/144.1.kubecon-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/144.1.kubecon-1200w.webp\" width=\"1200\" height=\"800\" alt=\"Dapr project kiosk\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Mike, Oli, and Marc at the Dapr project kiosk (Picture by CNCF)</figcaption></figure><p>From April 1st to April 4th, I was at <a href=\"https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/\" rel=\"noopener\">KubeCon Europe</a> in London. It was a very busy conference for me since I was wearing multiple (Dapr 😉) hats that week; doing booth duty (at two locations) and giving two Dapr sessions!</p><p>From Wednesday to Friday, I was managing the Dapr OSS kiosk, which is part of the project pavilion with other CNCF projects. I had tremendous help from Oli Tomlinson (Dapr STC member) and Mike Nguyen (Dapr maintainer) to run the kiosk. We had many people coming over to the booth and Oli and Mike did a great job explaining the Dapr concepts and showing code from the <a href=\"https://github.com/dapr/quickstarts/\" rel=\"noopener\">Dapr Quickstarts repo</a>. A special shoutout goes out to Manuel Zapf, who came over to the kiosk just in time to help answer a question about cross cluster communication with Dapr and Cilium 💪. Oli wrote a great recap from his perspective on <a href=\"https://www.linkedin.com/posts/oliver-tomlinson-59ba1365_huge-shout-out-to-marc-duiker-and-mike-activity-7315347041050656768-N_ls\" rel=\"noopener\">LinkedIn</a>.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/144.2.kubecon-440w.webp 440w, /assets/images/144.2.kubecon-650w.webp 650w, /assets/images/144.2.kubecon-960w.webp 960w, /assets/images/144.2.kubecon-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/144.2.kubecon-1200w.webp\" width=\"1200\" height=\"813\" alt=\"Diagrid booth\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Diagrid booth</figcaption></figure><p><a href=\"https://diagrid.io\" rel=\"noopener\">Diagrid</a> had a commercial booth at KubeCon, and I was also helping out there from time to time. Unfortunately, the Dapr kiosk and Diagrid booth were quite far apart; my feet still hurt from all the walking I did that week! 😅 My colleagues did an amazing job there, not just at the booth, but also delivering a couple of Dapr sessions, and taking part in panel discussions.</p><p>I was very lucky to give two sessions at the conference! The first one was a project lightning talk on Tuesday, where I highlighted that Dapr is now a graduated project, the workflow API has become stable with Dapr version 1.15, and Dapr now has AI capabilities with the <a href=\"https://docs.dapr.io/developing-applications/building-blocks/conversation/conversation-overview/\" rel=\"noopener\">LLM Conversation API</a> and <a href=\"https://github.com/dapr/dapr-agents\" rel=\"noopener\">Dapr Agents</a>.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/144.3.kubecon-440w.webp 440w, /assets/images/144.3.kubecon-650w.webp 650w, /assets/images/144.3.kubecon-960w.webp 960w, /assets/images/144.3.kubecon-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/144.3.kubecon-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Marc Duiker KubeCon session\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Marc Duiker giving the Failure is Not an Option session (picture by Manuel Zapf)</figcaption></figure><p>My second session was a regular breakout session on Friday. The session was titled <em>‘Failure is not an option: Durable Execution + Dapr = 🚀’</em> and covered the concepts of durable execution, the Dapr workflow engine, workflow patterns, and demoing a Dapr workflow app. I was pleasantly surprised to see the room for 80% full for a Friday afternoon session! Thanks to everyone who attended my session, and especially the people who dropped by afterward for questions.</p><p>For my session, I used <a href=\"https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-demo-time\" rel=\"noopener\">DemoTime</a> again, a VSCode extension created by <a href=\"https://bsky.app/profile/eliostruyf.com\" rel=\"noopener\">Elio Struyf</a>, to navigate through my slides (Markdown), code, and also execute the <code>dapr run</code> commands for running the Dapr demos.</p><p>This is the <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" rel=\"noopener\">repo with the demos</a>. It comes with a devcontainer configuration so you can run it yourself easily, either on GitHub Codespaces or locally. And you can use DemoTime to run through the slides and demos yourself.</p><p>Thanks again to everyone for making KubeCon a success! It was great meeting up with my cloud native friends again!</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2025-04-04T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-futuretech-2025/",
      "url": "https://marcduiker.dev/articles/speaking-at-futuretech-2025/",
      "title": "Speaking at FutureTech 2025",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/143.1.futuretech-440w.webp 440w, /assets/images/143.1.futuretech-650w.webp 650w, /assets/images/143.1.futuretech-960w.webp 960w, /assets/images/143.1.futuretech-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/143.1.futuretech-1200w.webp\" width=\"1200\" height=\"527\" alt=\"Marc at FutureTech\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Me at FutureTech, and Edwin &amp; Sander running a Dapr workshop</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" target=\"_blank\">Code demos on GitHub</a></p><hr><p>On March 13th, I was at <a href=\"https://futuretech.nl/\" rel=\"noopener\">FutureTech</a> in the Netherlands to give my session <em>‘Failure is not an option: Durable Execution + Dapr = 🚀’</em>.</p><p>FutureTech is an in-person conference in the Netherlands that focuses on Microsoft technologies, such as .NET &amp; Azure. This year the opening was very special, two of the organizers, Anjuli Jhakry and Dennis Vroegop, took the stage and performed a duet of Shallow by Lady Gaga &amp; Bradley Cooper. Kudos to them for their courage and great performance! 💪</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/143.2.futuretech-440w.webp 440w, /assets/images/143.2.futuretech-650w.webp 650w, /assets/images/143.2.futuretech-960w.webp 960w, /assets/images/143.2.futuretech-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/143.2.futuretech-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Anjuli and Dennis on stage after their performance\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Anjuli and Dennis after their performance (Picture by FutureTech)</figcaption></figure><p>My session was near the end of the afternoon, and I was happy to see so many people at my session, eager to learn more about durable execution with Dapr Workflow!</p><p>For my session, I used <a href=\"https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-demo-time\" rel=\"noopener\">DemoTime</a> again, a VSCode extension created by <a href=\"https://bsky.app/profile/eliostruyf.com\" rel=\"noopener\">Elio Struyf</a>, to navigate through my slides (Markdown) and code, and also execute the <code>dapr run</code> commands for running my Dapr demos. Elio keeps improving this extension and I can recommend it to everyone who does live coding demos using VSCode!</p><p>This is the <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" rel=\"noopener\">repo with the demos</a>. It comes with a devcontainer configuration so you can run it yourself easily, either on GitHub Codespaces or locally. And you can use DemoTime to run through the slides and demos.</p><p>It’s good to see more sessions &amp; workshops about distributed applications these days, such as the Microsoft Orleans session by Lindsay Broos, and the Dapr workshop by Edwin van Wijk and Sander Molenkamp.</p><p>I really enjoyed catching up with other members of the community. I spoke with Pieter de Bruin about improving the Dapr docs and developer experience in general. And Rick van den Bosch invited me to speak at <a href=\"https://www.dotnetzuid.nl/\" rel=\"noopener\">DotNetZuid</a>, thanks!</p><p>A big thank you to all the FutureTech organizers for organizing a great developer event, and for having me present about Dapr. And thanks to the attendees for joining my session (and giving me feedback)!</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2025-03-14T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-bitbash-2025/",
      "url": "https://marcduiker.dev/articles/speaking-at-bitbash-2025/",
      "title": "Speaking at Bitbash 2025",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/141.1.bitbash-440w.webp 440w, /assets/images/141.1.bitbash-650w.webp 650w, /assets/images/141.1.bitbash-960w.webp 960w, /assets/images/141.1.bitbash-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/141.1.bitbash-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Marc presenting\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Speaking at Bitbash (Thanks to Daniel Paulus for taking this picture!)</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" target=\"_blank\">Code demos on GitHub</a></p><hr><p>On Jan 25th, I was at <a href=\"https://www.bitbash.nl/\" rel=\"noopener\">Bitbash</a> in the Netherlands to give my session <em>‘Failure is not an option: Durable Execution + Dapr = 🚀’</em>.</p><p>Bitbash is quite a new in-person conference in the Netherlands that focuses on Microsoft technology, such as .NET &amp; Azure. The special thing about this conference is that it is completely free for attendees! This was the second edition of this conference and the theme this year was “Haunted”. The organizers hired “The Ghostbusters” who were walking around in full gear, hunting for bugs that haunt our software 👻.</p><p>The two-day conference started with a workshop day, where attendees could follow a half-day workshop. It was great to see Edwin van Wijk and Sander Molenkamp, both <a href=\"https://dapr.io/community/program/\" rel=\"noopener\">Dapr Meteors</a>, giving another workshop on Dapr! The second day was the main conference day, with 2 tracks.</p><p>My session was the first one of the day and started at 9:15 in the morning 🫠. I was happy to see so many enthusiastic people in the room who sacrificed their Saturday morning to learn about durable execution with Dapr Workflow!</p><p>For my session, I used <a href=\"https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-demo-time\" rel=\"noopener\">DemoTime</a>, a VSCode extension created by <a href=\"https://bsky.app/profile/eliostruyf.com\" rel=\"noopener\">Elio Struyf</a>, to navigate through my slides (Markdown) and code, and also execute the <code>dapr run</code> commands for my demos. It worked out really well, give DemoTime a try!</p><p>I got a very nice “haunted” speaker gift, a Slimer Funko Pop! and a metal speaker certificate.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/141.2.bitbash-440w.webp 440w, /assets/images/141.2.bitbash-650w.webp 650w, /assets/images/141.2.bitbash-960w.webp 960w, /assets/images/141.2.bitbash-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/141.2.bitbash-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Speaker Gift\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>The speaker gift was a great addition to my Ghostbusters Lego set!</figcaption></figure><p>This is the <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" rel=\"noopener\">repo with the demos</a>. It comes with a devcontainer configuration so you can run it yourself easily, either on GitHub Codespaces or locally. And you can use DemoTime to run through the slides and demos.</p><p>Thanks to the Bitbash organizers for having me, and thanks to the attendees for joining my session!</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/141.3.bitbash-440w.webp 440w, /assets/images/141.3.bitbash-650w.webp 650w, /assets/images/141.3.bitbash-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/141.3.bitbash-960w.webp\" width=\"960\" height=\"828\" alt=\"LinkedIn message fom Franco\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Thank you Franco van Zyl for sharing this 🙏.</figcaption></figure><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2025-01-27T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-the-net-amsterdam-meetup/",
      "url": "https://marcduiker.dev/articles/speaking-at-the-net-amsterdam-meetup/",
      "title": "Speaking at the .NET Amsterdam Meetup",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/142.1.dotnet-ams-440w.webp 440w, /assets/images/142.1.dotnet-ams-650w.webp 650w, /assets/images/142.1.dotnet-ams-960w.webp 960w, /assets/images/142.1.dotnet-ams-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/142.1.dotnet-ams-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Marc and the Dapr hat\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Speaking at .NET Amsterdam Meetup hosted by JetBrains. Thank you Eric for the photo!</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://github.com/dapr/quickstarts\" rel=\"noopener\">Dapr Quickstarts on GitHub</a> | <a href=\"https://docs.dapr.io/contributing/presentations/\" rel=\"noopener\">Dapr slide deck</a></p><hr><p>On Jan 8th, I gave my first presentation of the year at the <a href=\"https://www.meetup.com/dotnet-amsterdam/\" rel=\"noopener\">.NET Amsterdam Meetup</a>! This meetup is close to my heart since I’ve attended many of their meetups in the past and also got the opportunity to speak there a couple of times.</p><p>The meetup was hosted by <a href=\"https://www.jetbrains.com/\" rel=\"noopener\">JetBrains</a> in their beautiful Amsterdam office. I was welcomed by Sasha Kolesova, the product marketing manager for .NET tools at JetBrains, and the .NET meetup organizers, Erik Lieben and Thimo van Leeuwen.</p><p>The meetup was themed around <a href=\"https://dapr.io/\" rel=\"noopener\">Dapr</a>, I started with a Dapr introduction session, explaining the Dapr building blocks and doing demos on switching out state store components and showing the built-in resiliency features. The demos I used are available in the <a href=\"https://github.com/dapr/quickstarts\" rel=\"noopener\">Dapr Quickstarts</a> repo on GitHub.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/142.2.dotnet-ams-440w.webp 440w, /assets/images/142.2.dotnet-ams-650w.webp 650w, /assets/images/142.2.dotnet-ams-960w.webp 960w, /assets/images/142.2.dotnet-ams-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/142.2.dotnet-ams-1200w.webp\" width=\"1200\" height=\"1200\" alt=\"Marc and his hat\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>I started with explaining why I'm wearing the hat. Thanks to Sasha Kolesova for taking the picture!</figcaption></figure><p>The second session was by Florian van Dillen, who demonstrated in great detail how you can use .NET Aspire with Dapr to create distributed applications suitable for production. His demo application can be found <a href=\"https://github.com/fvandillen/dapr-aspire\" rel=\"noopener\">here</a>.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/142.4.dotnet-ams-440w.webp 440w, /assets/images/142.4.dotnet-ams-650w.webp 650w, /assets/images/142.4.dotnet-ams-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/142.4.dotnet-ams-960w.webp\" width=\"960\" height=\"720\" alt=\"Florian speaking\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Florian van Dillen speaking about .NET Aspire and Dapr.</figcaption></figure><p>Thank you Erik &amp; Thimo, for organizing this excellent Dapr 🎩 themed meetup, and Sasha, for hosting at this cool location and providing us with delicious pizza! 🍕</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2025-01-08T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/changing-my-website-tech-stack-again/",
      "url": "https://marcduiker.dev/articles/changing-my-website-tech-stack-again/",
      "title": "Changing my website tech stack (again)",
      "content_html": "<p>It seems that every couple of years, I change my website, not just the design, but nearly the complete tech stack 😬. In 2016, I moved to <a href=\"https://jekyllrb.com/\" rel=\"noopener\">Jekyll</a>, in 2021, I chose <a href=\"https://nuxt.com/\" rel=\"noopener\">Nuxt</a>, and now, at the end of 2024, I’ve moved it all to <a href=\"https://www.11ty.dev/\" rel=\"noopener\">Eleventy</a>.</p><blockquote><p>TLDR: When looking at a new static site generator, look at the starter kits/themes/templates, and check if they are actively maintained. It doesn’t really matter which generator you choose, but a good starter kit with proper docs and community support can save you a lot of headache. Also check what content migration steps are required (for example permalinks &amp; images).</p></blockquote><h2 id=\"if-it-isnt-broken-dont-try-to-fix-it\"><a href=\"#if-it-isnt-broken-dont-try-to-fix-it\" class=\"heading-anchor\">If it isn’t broken, don’t try to fix it</a></h2><p>I was pretty happy with my Nuxt setup that I used for the last 3.5 years. I understand just enough Vue to be able to update the layouts to my needs, and I wrote all my content in Markdown, as I love to do. Blogging life was good.</p><p>The front-end world moves quickly though, and I added a Dependabot configuration to create PRs to handle all the npm package updates. Most of the time this worked well, until there were some breaking changes… and I couldn’t easily figure out how to fix it 😭. I also didn’t want to roll back since some packages contained vulnerabilities.</p><p>I do think updating dependencies in general is a good thing, keeping everything secure and preventing software rot. But this breaking change was something I couldn’t fix, and I got <strong>so frustrated</strong> by the whole thing. To be fair, this isn’t an issue with Nuxt. I chose a theme that was not so popular, received very little updates, and therefore got out of sync with Nuxt itself. I was too eager updating something I didn’t fully understand the impact of. I think all static site generator themes/templates suffer from this. And while updating dependencies is important, <strong>if it isn’t broken, don’t try to ‘fix’ it</strong>.</p><p>So instead of fixing my Nuxt setup, I decided to start over with a new tech stack… again.</p><h2 id=\"choosing-a-new-static-site-generator\"><a href=\"#choosing-a-new-static-site-generator\" class=\"heading-anchor\">Choosing a new static site generator</a></h2><p>I really like static site generators! They are fast, deploying is relatively easy, and hosting is cheap.</p><p>There are a ton of static site generators out there. If you’re looking for one, I recommend looking at <a href=\"https://jamstack.org/generators/\" rel=\"noopener\">jamstack.org/generators/</a>.</p><p>I have experience with Jekyll, Hugo, Nuxt, and now I wanted to try something new because I like to learn new things 😁 (and apparently I’m a <em>static site generator masochist</em> 🫠).</p><p>These are my criteria for choosing a static site generator:</p><ul class=\"list\"><li>Flexible but doesn’t require a PhD in computer science to change/extend it.</li><li>Actively maintained project (incl the themes/templates!).</li><li>Enough starter kits/themes/templates available to start with and update it to my needs.</li><li>Good documentation.</li></ul><p>I heard good things about Astro and Eleventy recently. I looked at some templates from both projects and liked the <a href=\"https://astro.build/themes/details/astrowind/\" rel=\"noopener\">AstroWind theme</a> a lot, so I gave that a try. I started to migrate my blog posts, but somewhere I got stuck, and the site didn’t render at all 🫤. This is definitely a ‘me problem’, not an Astro problem. But since I couldn’t easily figure out what the issue was, I decided to move away from Astro.</p><blockquote><p>In general, I think a lot of static site generators can benefit from better error messages. I often found those messages to generic to be helpful. If something is wrong with a blog post I wrote, then please let me know which front-matter field is missing or incorrect!</p></blockquote><h2 id=\"eleventy\"><a href=\"#eleventy\" class=\"heading-anchor\">Eleventy</a></h2><p>The next generator on the list to try was Eleventy. I found a great template by <a href=\"https://bsky.app/profile/lenesaile.com\" rel=\"noopener\">Lene Saile</a>, named <a href=\"https://github.com/madrilene/eleventy-excellent\" rel=\"noopener\">Eleventy Excellent</a>.</p><p>One of the features I like a lot in this template is <em>image optimization</em>, this was something I didn’t have in my Nuxt setup. This also proved to be a bit of a challenge while migrating, since this Eleventy template was not configured to handle animated gifs, and this is something I use <strong>a lot</strong> to show my pixel art and UI animations in blog posts. Lene was very helpful and made a couple of updates to the template. Thanks Lene! 🙏</p><p>The other big change I had to do was some ‘content massaging’ for the blog posts. Since I wanted the URLs to stay the same, I had to add <em>permalinks</em> to all my blog posts. I did this manually for 90 posts, but I could have automated this with a script. Also, the image links had to be updated to use the new image optimization feature.</p><p>I spent quite some hours on migrating the posts. It all seems to be in a good state now, and I’m already writing new posts without being frustrated! 🎉</p><p>I wonder how long this setup will last… 🤔</p>",
      "date_published": "2024-12-27T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-cloudbrew-2024/",
      "url": "https://marcduiker.dev/articles/speaking-at-cloudbrew-2024/",
      "title": "Speaking at CloudBrew 2024",
      "content_html": "<figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/140.5.cloudbrew-440w.webp 440w, /assets/images/140.5.cloudbrew-650w.webp 650w, /assets/images/140.5.cloudbrew-960w.webp 960w, /assets/images/140.5.cloudbrew-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/140.5.cloudbrew-1200w.webp\" width=\"1200\" height=\"799\" alt=\"Marc presenting\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Speaking at CloudBrew (Thanks to Anthony Chu for taking this picture!)</figcaption></figure><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" target=\"_blank\">Code demos on GitHub</a></p><hr><p>On December 12 &amp; 13, I was at <a href=\"https://www.cloudbrew.be/\" rel=\"noopener\">CloudBrew</a> in Belgium to give my session <em>‘Failure is not an option: Durable Execution + Dapr = 🚀’</em>.</p><p>CloudBrew is a conference with a focus on cloud technologies, primarily Microsoft Azure. It’s located in a former brewery in Mechelen, a place close to Antwerp. I highly recommend visiting this city, it has a lovely pittoresque city center.</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/140.1.cloudbrew-440w.webp 440w, /assets/images/140.1.cloudbrew-650w.webp 650w, /assets/images/140.1.cloudbrew-960w.webp 960w, /assets/images/140.1.cloudbrew-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/140.1.cloudbrew-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Stickers\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Handed out many Dapr and pixelart stickers!</figcaption></figure><p>At the conference, it was amazing to see all the Dapr coverage across several sessions! Thank you Alex Mang, Eduard Keilholz, and Anthony Chu for mentioning Dapr in your sessions! 🙏</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/140.2.cloudbrew-440w.webp 440w, /assets/images/140.2.cloudbrew-650w.webp 650w, /assets/images/140.2.cloudbrew-960w.webp 960w, /assets/images/140.2.cloudbrew-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/140.2.cloudbrew-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Alex Mang presenting\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Alex Mang presenting about Dapr</figcaption></figure><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/140.3.cloudbrew-440w.webp 440w, /assets/images/140.3.cloudbrew-650w.webp 650w, /assets/images/140.3.cloudbrew-960w.webp 960w, /assets/images/140.3.cloudbrew-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/140.3.cloudbrew-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Eduard Keilholz presenting\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Eduard Keilholz presenting about Container Apps &amp; Dapr</figcaption></figure><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/140.4.cloudbrew-440w.webp 440w, /assets/images/140.4.cloudbrew-650w.webp 650w, /assets/images/140.4.cloudbrew-960w.webp 960w, /assets/images/140.4.cloudbrew-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/140.4.cloudbrew-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Marc &amp; Anthony\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>Was great meeting Anthony Chu IRL!</figcaption></figure><p>For my session, I tried a new approach: I used <a href=\"https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-demo-time\" rel=\"noopener\">DemoTime,</a> a VSCode extension created by <a href=\"https://bsky.app/profile/eliostruyf.com\" rel=\"noopener\">Elio Struyf</a>, to navigate through my slides (Markdown) and code, and also execute the <code>dapr run</code> commands for my demos. It worked out really well, give DemoTime a try!</p><figure slot=\"image\" class=\"flow\"><picture><source type=\"image/webp\" srcset=\"/assets/images/140.6.demotime-440w.webp 440w, /assets/images/140.6.demotime-650w.webp 650w, /assets/images/140.6.demotime-830w.webp 830w\" sizes=\"90vw\"><img src=\"/assets/images/140.6.demotime-830w.webp\" width=\"830\" height=\"333\" alt=\"DemoTime\" loading=\"lazy\" decoding=\"async\"></picture><figcaption>I'm using DemoTime to navigate through my slides and code</figcaption></figure><p>This is the <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" rel=\"noopener\">repo with the demos</a>. It comes with a devcontainer configuration so you can run it yourself easily, either on GitHub Codespaces or locally.</p><p>I had a lovely time at CloudBrew again this year. I like the cozy scale of this conference, and it’s clear the organizers have been doing this for many years. I had good conversations with Dapr enthusiasts (I hope to welcome you on the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a>!) and it was great meeting with other speakers and the organizers again. Until next time!</p><hr><p>Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2024-12-12T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/event-sponsor-checklist/",
      "url": "https://marcduiker.dev/articles/event-sponsor-checklist/",
      "title": "Event sponsor checklist",
      "content_html": "<p>I’ve been responsible for quite some event sponsorships that involve a sponsor booth with staff. I’ve condensed all the actions into this checklist that so you (and I) don’t have to start from scratch every time we sponsor an event!</p><p><em>The source is on <a href=\"https://github.com/marcduiker/event-sponsor-checklist\" rel=\"noopener\">GitHub</a>, please submit a PR if you have more tasks to add!</em></p><h2 id=\"before-the-event\"><a href=\"#before-the-event\" class=\"heading-anchor\">Before the event</a></h2><ul class=\"list\"><li>Write down what the goal is for sponsoring the event and inform everyone involved about this goal.</li><li>Check if the event has a Code of Conduct (CoC) with contact information.</li><li>Sign up to sponsor the event.</li><li>Pay for the sponsorship.</li><li>Order additional booth equipment (monitors) or furniture (tables/desks/laptop stands).</li><li>Order laptops/tablets for demos/presentations.</li><li>Design &amp; order a booth backdrop or roll-up banner.</li><li>Design datasheets/flyers to handout at the booth.</li><li>Order paper datasheets/flyers. Or choose the environmental consious option and use QR codes at the booth.</li><li>Design &amp; order stickers.</li><li>If you have datasheets/flyers/stickers: order display stands for these so your booth stays tidy.</li><li>Create a slide deck or video to play on a big monitor at the booth (or do something fun and create a game!).</li><li>Find &amp; order sustainable swag or prizes to give away at the booth.</li><li>Decide what is the <em>Best Next Step</em> for a booth visitor once they talked to you? Do you point them to your website, do you want them to book a meeting? Prepare a document with QR codes or create an online form to facilitate these <em>Best Next Steps</em>.</li><li>Prepare a <a href=\"#know-before-you-go-document\"><em>Know Before You Go</em></a> document and share this with the people who will staff the booth.</li><li>Send calendar invites to the booth staff so they know when they should be at the booth. Include a link to the <em>Know Before You Go</em> doc).</li><li>Set up lead scanning. Can the default scoring/questions be changed?</li><li>Label all the hardware that you’ll bring to the booth with company name / email. Ensure everything is insured.</li><li>If the booth staff are staying overnight, schedule a team dinner for them.</li><li>Setup a group chat for the booth staff using your fav communication tool that everyone has on their phone. Use this to quickly get in touch with each other.</li><li>Things to bring to the event: breath mints, toothbrush, pain killers, blister patches, band aids, Dextro energy tablets.</li></ul><h3 id=\"know-before-you-go-document\"><a href=\"#know-before-you-go-document\" class=\"heading-anchor\">Know Before You Go document</a></h3><p>The Know Before You Go (KBYG) document contains practical information for everyone staffing the booth.</p><ul class=\"list\"><li>Contains links to:<ul class=\"list\"><li>Event website</li><li>Google Maps of the venue location</li><li>Code of Conduct of the event</li></ul></li><li>Contact details of event organizers, sponsor/technical support, code of conduct team.</li><li>The booth schedule: who will be on the booth, on which days including start and end times.<ul class=\"list\"><li>Include when the breaks between sessions are, since those are the busiest times at the booth.</li><li>Who will set up the booth?</li><li>Who will break down the booth?</li><li>Who will pick up and return the lead scanner?</li></ul></li><li>Prepare a script how to start a conversation when attendees stop by the booth.<ul class=\"list\"><li>Questions to find out if the attendee is part of your target audience.</li><li>Concrete examples of how your product/service is helping organizations. Use specific numbers to indicate increase in efficiency or reduction in cost.</li></ul></li><li>Describe the <em>Next Best Steps</em> for your audience.</li></ul><h2 id=\"during-the-event\"><a href=\"#during-the-event\" class=\"heading-anchor\">During the event</a></h2><ul class=\"list\"><li>Pick up the lead scanner.</li><li>Set up the booth backdrop / roll-up banner.</li><li>Prepare the booth with datasheets/flyers/stickers/swag.</li><li>Talk to people who show interest. Don’t harrass people just to get more badge scans. Be kind and act how <em>you</em> would like to be approached. Use the script(s) from the KBYG doc and use the <em>Best Next Steps</em> that are suitable for the attendees.</li><li>Drink water and stay hydrated.</li><li>Take lots of pictures. Ask for consent if these pictures include event attendees. Share the pictures on social media during the day or immediately after.</li></ul><h2 id=\"after-the-event\"><a href=\"#after-the-event\" class=\"heading-anchor\">After the event</a></h2><ul class=\"list\"><li>Write an <a href=\"#event-report-document\"><em>Event Report</em></a> that summarizes the event.</li><li>Give feedback to the event organizers based on the event report.</li><li>Send post-event social messages thanking the organizers and attendees. Include some of the pictures.</li><li>If relevant sessions have been recorded on video, share them via social media once they are published.</li></ul><h3 id=\"event-report-document\"><a href=\"#event-report-document\" class=\"heading-anchor\">Event Report document</a></h3><p>Questions to answer in an event report:</p><ul class=\"list\"><li>Was the goal that you set met?</li><li>What went well?</li><li>What didn’t go well?</li><li>What are concrete improvement points for next time?</li><li>How many datasheets/stickers/swag were ordered. Was this enough?</li><li>What was the overal cost of sponsoring?</li><li>Is it worth sponsoring this event again?</li></ul><h2 id=\"contributing-to-this-checklist\"><a href=\"#contributing-to-this-checklist\" class=\"heading-anchor\">Contributing to this checklist</a></h2><p>Do you have more tasks that can be included in this list? <a href=\"https://github.com/marcduiker/event-sponsor-checklist\" rel=\"noopener\">Submit a PR please</a>! 🙏</p>",
      "date_published": "2024-11-28T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/organizing-and-co-hosting-dapr-day-daprs-5th-anniversary/",
      "url": "https://marcduiker.dev/articles/organizing-and-co-hosting-dapr-day-daprs-5th-anniversary/",
      "title": "Organizing and co-hosting Dapr Day - Dapr&#39;s 5th anniversary",
      "content_html": "<p>On October 16th 2024, the <a href=\"https://dapr.io\" rel=\"noopener\">Dapr OSS</a> project turned 5 years old! 🎉 To celebrate this milestone, I organized a virtual event called Dapr Day, together with <a href=\"https://www.cncf.io/\" rel=\"noopener\">the CNCF</a>.</p><p>This post covers some of the larger aspects of organizing and co-hosting this virtual event, and includes a link to a recordings on YouTube.</p><h2 id=\"organizing\"><a href=\"#organizing\" class=\"heading-anchor\">Organizing</a></h2><p>Events organized with the CNCF have several requirements, to ensure the events are fair and inclusive. More info about the various CNCF events and their requirements can be found <a href=\"https://www.cncf.io/events/\" rel=\"noopener\">here</a>.</p><p>Organizing an event starts many months before the event itself. We first decided on the actual event date, and worked back the timeline and actions from there.</p><p>The first big thing that was required was establishing a program committee. This committee is responsible for selecting the talks and speakers for the event. The members of the program committee were: Mark Fussell (Diagrid), Cassie Coyle (Diagrid), Oliver Tomlinson (Dotmatics), and Paul Yuknewicz (Microsoft).</p><p>I setup the call for papers (CfP) using <a href=\"https://sessionize.com\" rel=\"noopener\">Sessionize</a>, a platform that makes CfP management easy for both organizers and speakers. Once CfP was closed, the program committee first did an async review individually before we met together to discuss the session. This is always a tricky part because everyone has some favorites, and we have to make sure we end up with a good mix of topics and speakers.</p><p>We approached two keynote speakers ouside the CfP process: Mark Russinovich (CTO of Microsoft Azure) and Joe Beda (Kubernetes co-creator). Both were very enthusiastic about speaking at Dapr Day, and we were very happy to have them join.</p><p>After the final speaker selection was made, I sent out invites to the speakers with instructions on how to prepare for the event.</p><h2 id=\"co-hosting\"><a href=\"#co-hosting\" class=\"heading-anchor\">Co-hosting</a></h2><p>For about a year now, I’ve been alternating the hosting of the Dapr Community Calls with <a href=\"https://bsky.app/profile/cecilphillip.bsky.social\" rel=\"noopener\">Cecil Phillip</a>, who is one of the Dapr Community Managers. Cecil is a great host, and has a lot of experience with live streaming, so I asked him to co-host Dapr Day with me. Together with Annu Singh, another Dapr community manager (mostly working behind the scenes) we brain-stormed how we can make event engaging and fun for the attendees. We took some successful ingredients from the Dapr Day that we organized in April 2024 and expanded on that.</p><p>Aside from some initial audio feedback loop issues, the co-hosting for the event went very well. I really enjoyed doing a Kahoot quiz where we asked the audience questions about the history of the Dapr project. The tricky thing with live streaming and interacting with the audience is the delay. We were using Restream to stream to the CNCF YouTube channel and the delay was around 25 seconds. I made sure the Kahoot answering time was long enough to compensate for this delay.</p><p>Thanks to all the speakers, the program committee, the CNCF, and the Dapr community for making this event a success! 🙏</p><h2 id=\"watch-the-recordings\"><a href=\"#watch-the-recordings\" class=\"heading-anchor\">Watch the recordings</a></h2><div><custom-youtube class=\"flow\"><!-- component composition by: https://github.com/zachleat/zachleat.com -->\n<style>\n  /* Hide without JS */\n  is-land:not(:defined).video-wrapper {\n    display: none;\n  }\n</style>\n\n  <is-land on:visible class=\"video-wrapper\">\n    <lite-youtube videoid=\"eSGpryW_sbs\" js-api playlabel=\"Play: Dapr Day Oct 2024 - Opening Keynote by Mark Russinovich\" style=\"background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DeSGpryW_sbs/auto/jpeg/')\"></lite-youtube>\n\n    <template data-island=\"once\">\n      <style>\n        lite-youtube {\n          max-inline-size: 100% !important;\n          background-size: cover;\n        }\n\n        is-land lite-youtube {\n          background-color: #eee;\n          border-radius: 0.5em;\n          background-size: cover;\n        }\n        is-land[ready] lite-youtube {\n          /* gotta set in `style` to override the 480w image from lite-youtube */\n          --yt-poster-img-url: var(--yt-poster-img-url-lazy);\n        }\n\n        .video-wrapper {\n          aspect-ratio: 16 / 9;\n          width: 100%;\n        }\n\n        is-land.video-wrapper {\n          display: block;\n        }\n      </style>\n      <link rel=\"stylesheet\" href=\"/assets/components/lite-yt-embed.css\" />\n      <script type=\"module\" src=\"/assets/components/lite-yt-embed.js\"></script>\n    </template>\n  </is-land>\n\n<custom-youtube-link label=\"Dapr Day Oct 2024 - Opening Keynote by Mark Russinovich\" href=\"https://youtube.com/watch?v=eSGpryW_sbs\"><style>\n  custom-youtube-link {\n    display: flex;\n    align-items: flex-start;\n    gap: var(--space-xs);\n    font-size: var(--size-step-min-1);\n  }\n  custom-youtube-link svg {\n    font-size: var(--size-step-0);\n  }\n</style>\n\n<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" ariahidden=\"true\"><title>YouTube</title><g fill=\"currentColor\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1\"><path d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\"></path></g></svg>\n\n<a href=\"https://youtube.com/watch?v=eSGpryW_sbs\">Dapr Day Oct 2024 - Opening Keynote by Mark Russinovich</a>\n\n<!-- Inspired by https://github.com/zachleat/zachleat.com  -->\n</custom-youtube-link>\n</custom-youtube></div><div><custom-youtube class=\"flow\"><!-- component composition by: https://github.com/zachleat/zachleat.com -->\n<style>\n  /* Hide without JS */\n  is-land:not(:defined).video-wrapper {\n    display: none;\n  }\n</style>\n\n  <is-land on:visible class=\"video-wrapper\">\n    <lite-youtube videoid=\"5zG4a1FeY8A\" js-api playlabel=\"Play: Dapr Day Oct 2024 - Dapr &amp; Kubernetes at SharperImage.com: Experiences from the Trenches\" style=\"background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3D5zG4a1FeY8A/auto/jpeg/')\"></lite-youtube>\n\n    <template data-island=\"once\">\n      <style>\n        lite-youtube {\n          max-inline-size: 100% !important;\n          background-size: cover;\n        }\n\n        is-land lite-youtube {\n          background-color: #eee;\n          border-radius: 0.5em;\n          background-size: cover;\n        }\n        is-land[ready] lite-youtube {\n          /* gotta set in `style` to override the 480w image from lite-youtube */\n          --yt-poster-img-url: var(--yt-poster-img-url-lazy);\n        }\n\n        .video-wrapper {\n          aspect-ratio: 16 / 9;\n          width: 100%;\n        }\n\n        is-land.video-wrapper {\n          display: block;\n        }\n      </style>\n      <link rel=\"stylesheet\" href=\"/assets/components/lite-yt-embed.css\" />\n      <script type=\"module\" src=\"/assets/components/lite-yt-embed.js\"></script>\n    </template>\n  </is-land>\n\n<custom-youtube-link label=\"Dapr Day Oct 2024 - Dapr &amp; Kubernetes at SharperImage.com: Experiences from the Trenches\" href=\"https://youtube.com/watch?v=5zG4a1FeY8A\"><style>\n  custom-youtube-link {\n    display: flex;\n    align-items: flex-start;\n    gap: var(--space-xs);\n    font-size: var(--size-step-min-1);\n  }\n  custom-youtube-link svg {\n    font-size: var(--size-step-0);\n  }\n</style>\n\n<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" ariahidden=\"true\"><title>YouTube</title><g fill=\"currentColor\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1\"><path d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\"></path></g></svg>\n\n<a href=\"https://youtube.com/watch?v=5zG4a1FeY8A\">Dapr Day Oct 2024 - Dapr &amp; Kubernetes at SharperImage.com: Experiences from the Trenches</a>\n\n<!-- Inspired by https://github.com/zachleat/zachleat.com  -->\n</custom-youtube-link>\n</custom-youtube></div><div><custom-youtube class=\"flow\"><!-- component composition by: https://github.com/zachleat/zachleat.com -->\n<style>\n  /* Hide without JS */\n  is-land:not(:defined).video-wrapper {\n    display: none;\n  }\n</style>\n\n  <is-land on:visible class=\"video-wrapper\">\n    <lite-youtube videoid=\"ftg9Tp0Z494\" js-api playlabel=\"Play: Dapr Day Oct 2024 - Maintainers Roadmap by Yaron Schneider\" style=\"background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3Dftg9Tp0Z494/auto/jpeg/')\"></lite-youtube>\n\n    <template data-island=\"once\">\n      <style>\n        lite-youtube {\n          max-inline-size: 100% !important;\n          background-size: cover;\n        }\n\n        is-land lite-youtube {\n          background-color: #eee;\n          border-radius: 0.5em;\n          background-size: cover;\n        }\n        is-land[ready] lite-youtube {\n          /* gotta set in `style` to override the 480w image from lite-youtube */\n          --yt-poster-img-url: var(--yt-poster-img-url-lazy);\n        }\n\n        .video-wrapper {\n          aspect-ratio: 16 / 9;\n          width: 100%;\n        }\n\n        is-land.video-wrapper {\n          display: block;\n        }\n      </style>\n      <link rel=\"stylesheet\" href=\"/assets/components/lite-yt-embed.css\" />\n      <script type=\"module\" src=\"/assets/components/lite-yt-embed.js\"></script>\n    </template>\n  </is-land>\n\n<custom-youtube-link label=\"Dapr Day Oct 2024 - Maintainers Roadmap by Yaron Schneider\" href=\"https://youtube.com/watch?v=ftg9Tp0Z494\"><style>\n  custom-youtube-link {\n    display: flex;\n    align-items: flex-start;\n    gap: var(--space-xs);\n    font-size: var(--size-step-min-1);\n  }\n  custom-youtube-link svg {\n    font-size: var(--size-step-0);\n  }\n</style>\n\n<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" ariahidden=\"true\"><title>YouTube</title><g fill=\"currentColor\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1\"><path d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\"></path></g></svg>\n\n<a href=\"https://youtube.com/watch?v=ftg9Tp0Z494\">Dapr Day Oct 2024 - Maintainers Roadmap by Yaron Schneider</a>\n\n<!-- Inspired by https://github.com/zachleat/zachleat.com  -->\n</custom-youtube-link>\n</custom-youtube></div><div><custom-youtube class=\"flow\"><!-- component composition by: https://github.com/zachleat/zachleat.com -->\n<style>\n  /* Hide without JS */\n  is-land:not(:defined).video-wrapper {\n    display: none;\n  }\n</style>\n\n  <is-land on:visible class=\"video-wrapper\">\n    <lite-youtube videoid=\"gW7aHg_VQLw\" js-api playlabel=\"Play: Dapr Day Oct 2024 - Processing one million data points daily with Dapr\" style=\"background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DgW7aHg_VQLw/auto/jpeg/')\"></lite-youtube>\n\n    <template data-island=\"once\">\n      <style>\n        lite-youtube {\n          max-inline-size: 100% !important;\n          background-size: cover;\n        }\n\n        is-land lite-youtube {\n          background-color: #eee;\n          border-radius: 0.5em;\n          background-size: cover;\n        }\n        is-land[ready] lite-youtube {\n          /* gotta set in `style` to override the 480w image from lite-youtube */\n          --yt-poster-img-url: var(--yt-poster-img-url-lazy);\n        }\n\n        .video-wrapper {\n          aspect-ratio: 16 / 9;\n          width: 100%;\n        }\n\n        is-land.video-wrapper {\n          display: block;\n        }\n      </style>\n      <link rel=\"stylesheet\" href=\"/assets/components/lite-yt-embed.css\" />\n      <script type=\"module\" src=\"/assets/components/lite-yt-embed.js\"></script>\n    </template>\n  </is-land>\n\n<custom-youtube-link label=\"Dapr Day Oct 2024 - Processing one million data points daily with Dapr\" href=\"https://youtube.com/watch?v=gW7aHg_VQLw\"><style>\n  custom-youtube-link {\n    display: flex;\n    align-items: flex-start;\n    gap: var(--space-xs);\n    font-size: var(--size-step-min-1);\n  }\n  custom-youtube-link svg {\n    font-size: var(--size-step-0);\n  }\n</style>\n\n<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" ariahidden=\"true\"><title>YouTube</title><g fill=\"currentColor\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1\"><path d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\"></path></g></svg>\n\n<a href=\"https://youtube.com/watch?v=gW7aHg_VQLw\">Dapr Day Oct 2024 - Processing one million data points daily with Dapr</a>\n\n<!-- Inspired by https://github.com/zachleat/zachleat.com  -->\n</custom-youtube-link>\n</custom-youtube></div><div><custom-youtube class=\"flow\"><!-- component composition by: https://github.com/zachleat/zachleat.com -->\n<style>\n  /* Hide without JS */\n  is-land:not(:defined).video-wrapper {\n    display: none;\n  }\n</style>\n\n  <is-land on:visible class=\"video-wrapper\">\n    <lite-youtube videoid=\"8M1PqfZwGPg\" js-api playlabel=\"Play: Dapr Day Oct 2024 - Bindings for Event Sourcing and CQRS capabilities in Dapr applications\" style=\"background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3D8M1PqfZwGPg/auto/jpeg/')\"></lite-youtube>\n\n    <template data-island=\"once\">\n      <style>\n        lite-youtube {\n          max-inline-size: 100% !important;\n          background-size: cover;\n        }\n\n        is-land lite-youtube {\n          background-color: #eee;\n          border-radius: 0.5em;\n          background-size: cover;\n        }\n        is-land[ready] lite-youtube {\n          /* gotta set in `style` to override the 480w image from lite-youtube */\n          --yt-poster-img-url: var(--yt-poster-img-url-lazy);\n        }\n\n        .video-wrapper {\n          aspect-ratio: 16 / 9;\n          width: 100%;\n        }\n\n        is-land.video-wrapper {\n          display: block;\n        }\n      </style>\n      <link rel=\"stylesheet\" href=\"/assets/components/lite-yt-embed.css\" />\n      <script type=\"module\" src=\"/assets/components/lite-yt-embed.js\"></script>\n    </template>\n  </is-land>\n\n<custom-youtube-link label=\"Dapr Day Oct 2024 - Bindings for Event Sourcing and CQRS capabilities in Dapr applications\" href=\"https://youtube.com/watch?v=8M1PqfZwGPg\"><style>\n  custom-youtube-link {\n    display: flex;\n    align-items: flex-start;\n    gap: var(--space-xs);\n    font-size: var(--size-step-min-1);\n  }\n  custom-youtube-link svg {\n    font-size: var(--size-step-0);\n  }\n</style>\n\n<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" ariahidden=\"true\"><title>YouTube</title><g fill=\"currentColor\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1\"><path d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\"></path></g></svg>\n\n<a href=\"https://youtube.com/watch?v=8M1PqfZwGPg\">Dapr Day Oct 2024 - Bindings for Event Sourcing and CQRS capabilities in Dapr applications</a>\n\n<!-- Inspired by https://github.com/zachleat/zachleat.com  -->\n</custom-youtube-link>\n</custom-youtube></div><div><custom-youtube class=\"flow\"><!-- component composition by: https://github.com/zachleat/zachleat.com -->\n<style>\n  /* Hide without JS */\n  is-land:not(:defined).video-wrapper {\n    display: none;\n  }\n</style>\n\n  <is-land on:visible class=\"video-wrapper\">\n    <lite-youtube videoid=\"NSQVg-1UJGk\" js-api playlabel=\"Play: Dapr Day Oct 2024 - Dapr and .NET Aspire - A New Era for Observability\" style=\"background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DNSQVg-1UJGk/auto/jpeg/')\"></lite-youtube>\n\n    <template data-island=\"once\">\n      <style>\n        lite-youtube {\n          max-inline-size: 100% !important;\n          background-size: cover;\n        }\n\n        is-land lite-youtube {\n          background-color: #eee;\n          border-radius: 0.5em;\n          background-size: cover;\n        }\n        is-land[ready] lite-youtube {\n          /* gotta set in `style` to override the 480w image from lite-youtube */\n          --yt-poster-img-url: var(--yt-poster-img-url-lazy);\n        }\n\n        .video-wrapper {\n          aspect-ratio: 16 / 9;\n          width: 100%;\n        }\n\n        is-land.video-wrapper {\n          display: block;\n        }\n      </style>\n      <link rel=\"stylesheet\" href=\"/assets/components/lite-yt-embed.css\" />\n      <script type=\"module\" src=\"/assets/components/lite-yt-embed.js\"></script>\n    </template>\n  </is-land>\n\n<custom-youtube-link label=\"Dapr Day Oct 2024 - Dapr and .NET Aspire - A New Era for Observability\" href=\"https://youtube.com/watch?v=NSQVg-1UJGk\"><style>\n  custom-youtube-link {\n    display: flex;\n    align-items: flex-start;\n    gap: var(--space-xs);\n    font-size: var(--size-step-min-1);\n  }\n  custom-youtube-link svg {\n    font-size: var(--size-step-0);\n  }\n</style>\n\n<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" ariahidden=\"true\"><title>YouTube</title><g fill=\"currentColor\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1\"><path d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\"></path></g></svg>\n\n<a href=\"https://youtube.com/watch?v=NSQVg-1UJGk\">Dapr Day Oct 2024 - Dapr and .NET Aspire - A New Era for Observability</a>\n\n<!-- Inspired by https://github.com/zachleat/zachleat.com  -->\n</custom-youtube-link>\n</custom-youtube></div><div><custom-youtube class=\"flow\"><!-- component composition by: https://github.com/zachleat/zachleat.com -->\n<style>\n  /* Hide without JS */\n  is-land:not(:defined).video-wrapper {\n    display: none;\n  }\n</style>\n\n  <is-land on:visible class=\"video-wrapper\">\n    <lite-youtube videoid=\"pnSrzTCoLLM\" js-api playlabel=\"Play: Dapr Day Oct 2024 - Floki: Building Agentic Workflows with Dapr\" style=\"background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DpnSrzTCoLLM/auto/jpeg/')\"></lite-youtube>\n\n    <template data-island=\"once\">\n      <style>\n        lite-youtube {\n          max-inline-size: 100% !important;\n          background-size: cover;\n        }\n\n        is-land lite-youtube {\n          background-color: #eee;\n          border-radius: 0.5em;\n          background-size: cover;\n        }\n        is-land[ready] lite-youtube {\n          /* gotta set in `style` to override the 480w image from lite-youtube */\n          --yt-poster-img-url: var(--yt-poster-img-url-lazy);\n        }\n\n        .video-wrapper {\n          aspect-ratio: 16 / 9;\n          width: 100%;\n        }\n\n        is-land.video-wrapper {\n          display: block;\n        }\n      </style>\n      <link rel=\"stylesheet\" href=\"/assets/components/lite-yt-embed.css\" />\n      <script type=\"module\" src=\"/assets/components/lite-yt-embed.js\"></script>\n    </template>\n  </is-land>\n\n<custom-youtube-link label=\"Dapr Day Oct 2024 - Floki: Building Agentic Workflows with Dapr\" href=\"https://youtube.com/watch?v=pnSrzTCoLLM\"><style>\n  custom-youtube-link {\n    display: flex;\n    align-items: flex-start;\n    gap: var(--space-xs);\n    font-size: var(--size-step-min-1);\n  }\n  custom-youtube-link svg {\n    font-size: var(--size-step-0);\n  }\n</style>\n\n<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" ariahidden=\"true\"><title>YouTube</title><g fill=\"currentColor\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1\"><path d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\"></path></g></svg>\n\n<a href=\"https://youtube.com/watch?v=pnSrzTCoLLM\">Dapr Day Oct 2024 - Floki: Building Agentic Workflows with Dapr</a>\n\n<!-- Inspired by https://github.com/zachleat/zachleat.com  -->\n</custom-youtube-link>\n</custom-youtube></div><div><custom-youtube class=\"flow\"><!-- component composition by: https://github.com/zachleat/zachleat.com -->\n<style>\n  /* Hide without JS */\n  is-land:not(:defined).video-wrapper {\n    display: none;\n  }\n</style>\n\n  <is-land on:visible class=\"video-wrapper\">\n    <lite-youtube videoid=\"ewcSxd_yKfE\" js-api playlabel=\"Play: Dapr Day Oct 2024 - Building Real-World Applications with Dapr: A Startup Developer's Journey\" style=\"background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DewcSxd_yKfE/auto/jpeg/')\"></lite-youtube>\n\n    <template data-island=\"once\">\n      <style>\n        lite-youtube {\n          max-inline-size: 100% !important;\n          background-size: cover;\n        }\n\n        is-land lite-youtube {\n          background-color: #eee;\n          border-radius: 0.5em;\n          background-size: cover;\n        }\n        is-land[ready] lite-youtube {\n          /* gotta set in `style` to override the 480w image from lite-youtube */\n          --yt-poster-img-url: var(--yt-poster-img-url-lazy);\n        }\n\n        .video-wrapper {\n          aspect-ratio: 16 / 9;\n          width: 100%;\n        }\n\n        is-land.video-wrapper {\n          display: block;\n        }\n      </style>\n      <link rel=\"stylesheet\" href=\"/assets/components/lite-yt-embed.css\" />\n      <script type=\"module\" src=\"/assets/components/lite-yt-embed.js\"></script>\n    </template>\n  </is-land>\n\n<custom-youtube-link label=\"Dapr Day Oct 2024 - Building Real-World Applications with Dapr: A Startup Developer's Journey\" href=\"https://youtube.com/watch?v=ewcSxd_yKfE\"><style>\n  custom-youtube-link {\n    display: flex;\n    align-items: flex-start;\n    gap: var(--space-xs);\n    font-size: var(--size-step-min-1);\n  }\n  custom-youtube-link svg {\n    font-size: var(--size-step-0);\n  }\n</style>\n\n<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" ariahidden=\"true\"><title>YouTube</title><g fill=\"currentColor\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1\"><path d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\"></path></g></svg>\n\n<a href=\"https://youtube.com/watch?v=ewcSxd_yKfE\">Dapr Day Oct 2024 - Building Real-World Applications with Dapr: A Startup Developer's Journey</a>\n\n<!-- Inspired by https://github.com/zachleat/zachleat.com  -->\n</custom-youtube-link>\n</custom-youtube></div><div><custom-youtube class=\"flow\"><!-- component composition by: https://github.com/zachleat/zachleat.com -->\n<style>\n  /* Hide without JS */\n  is-land:not(:defined).video-wrapper {\n    display: none;\n  }\n</style>\n\n  <is-land on:visible class=\"video-wrapper\">\n    <lite-youtube videoid=\"xZyuO2dU9b0\" js-api playlabel=\"Play: Dapr Day Oct 2024 - The Dapr Actors Journey: From Understanding to Intuition\" style=\"background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DxZyuO2dU9b0/auto/jpeg/')\"></lite-youtube>\n\n    <template data-island=\"once\">\n      <style>\n        lite-youtube {\n          max-inline-size: 100% !important;\n          background-size: cover;\n        }\n\n        is-land lite-youtube {\n          background-color: #eee;\n          border-radius: 0.5em;\n          background-size: cover;\n        }\n        is-land[ready] lite-youtube {\n          /* gotta set in `style` to override the 480w image from lite-youtube */\n          --yt-poster-img-url: var(--yt-poster-img-url-lazy);\n        }\n\n        .video-wrapper {\n          aspect-ratio: 16 / 9;\n          width: 100%;\n        }\n\n        is-land.video-wrapper {\n          display: block;\n        }\n      </style>\n      <link rel=\"stylesheet\" href=\"/assets/components/lite-yt-embed.css\" />\n      <script type=\"module\" src=\"/assets/components/lite-yt-embed.js\"></script>\n    </template>\n  </is-land>\n\n<custom-youtube-link label=\"Dapr Day Oct 2024 - The Dapr Actors Journey: From Understanding to Intuition\" href=\"https://youtube.com/watch?v=xZyuO2dU9b0\"><style>\n  custom-youtube-link {\n    display: flex;\n    align-items: flex-start;\n    gap: var(--space-xs);\n    font-size: var(--size-step-min-1);\n  }\n  custom-youtube-link svg {\n    font-size: var(--size-step-0);\n  }\n</style>\n\n<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" ariahidden=\"true\"><title>YouTube</title><g fill=\"currentColor\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1\"><path d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\"></path></g></svg>\n\n<a href=\"https://youtube.com/watch?v=xZyuO2dU9b0\">Dapr Day Oct 2024 - The Dapr Actors Journey: From Understanding to Intuition</a>\n\n<!-- Inspired by https://github.com/zachleat/zachleat.com  -->\n</custom-youtube-link>\n</custom-youtube></div><div><custom-youtube class=\"flow\"><!-- component composition by: https://github.com/zachleat/zachleat.com -->\n<style>\n  /* Hide without JS */\n  is-land:not(:defined).video-wrapper {\n    display: none;\n  }\n</style>\n\n  <is-land on:visible class=\"video-wrapper\">\n    <lite-youtube videoid=\"qMhSjE3MwVE\" js-api playlabel=\"Play: Dapr Day Oct 2024 - Closing Keynote by Joe Beda\" style=\"background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DqMhSjE3MwVE/auto/jpeg/')\"></lite-youtube>\n\n    <template data-island=\"once\">\n      <style>\n        lite-youtube {\n          max-inline-size: 100% !important;\n          background-size: cover;\n        }\n\n        is-land lite-youtube {\n          background-color: #eee;\n          border-radius: 0.5em;\n          background-size: cover;\n        }\n        is-land[ready] lite-youtube {\n          /* gotta set in `style` to override the 480w image from lite-youtube */\n          --yt-poster-img-url: var(--yt-poster-img-url-lazy);\n        }\n\n        .video-wrapper {\n          aspect-ratio: 16 / 9;\n          width: 100%;\n        }\n\n        is-land.video-wrapper {\n          display: block;\n        }\n      </style>\n      <link rel=\"stylesheet\" href=\"/assets/components/lite-yt-embed.css\" />\n      <script type=\"module\" src=\"/assets/components/lite-yt-embed.js\"></script>\n    </template>\n  </is-land>\n\n<custom-youtube-link label=\"Dapr Day Oct 2024 - Closing Keynote by Joe Beda\" href=\"https://youtube.com/watch?v=qMhSjE3MwVE\"><style>\n  custom-youtube-link {\n    display: flex;\n    align-items: flex-start;\n    gap: var(--space-xs);\n    font-size: var(--size-step-min-1);\n  }\n  custom-youtube-link svg {\n    font-size: var(--size-step-0);\n  }\n</style>\n\n<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" ariahidden=\"true\"><title>YouTube</title><g fill=\"currentColor\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1\"><path d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\"></path></g></svg>\n\n<a href=\"https://youtube.com/watch?v=qMhSjE3MwVE\">Dapr Day Oct 2024 - Closing Keynote by Joe Beda</a>\n\n<!-- Inspired by https://github.com/zachleat/zachleat.com  -->\n</custom-youtube-link>\n</custom-youtube></div>",
      "date_published": "2024-10-16T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/sponsoring-techorama-nl-2024/",
      "url": "https://marcduiker.dev/articles/sponsoring-techorama-nl-2024/",
      "title": "Sponsoring Techorama NL 2024",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/139.1.techorama-440w.webp 440w, /assets/images/139.1.techorama-650w.webp 650w, /assets/images/139.1.techorama-960w.webp 960w, /assets/images/139.1.techorama-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/139.1.techorama-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Techorama selfie\" loading=\"lazy\" decoding=\"async\"></picture></p><p>On October 8 &amp; 9 I was at <a href=\"https://techorama.nl/\" rel=\"noopener\">Techorama Netherlands</a> because Diagrid was sponsoring this event. Techorama is the best .NET / Azure focused conference in the Netherlands, and I was really looking forward to be there. The Techorama events are always themed, this year it was a <em>fun fair</em> edition, and wow, they delivered! 🎉 The venue was decorated with a lot of fun fair elements, and there were carnival rides outside! 🎡</p><p>Diagrid was silver sponsor this year and I prepared a life-size pixelart sidecar motorcycle for our booth. Attendees ‘sat’ in the sidear and I took their picture with an instant camera. I had a lot of fun preparing for this and most people did get the reference of the sidecar (Dapr usses the sidecar pattern, since the Dapr process runs next to the application process).</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/139.6.techorama-sidecars-440w.webp 440w, /assets/images/139.6.techorama-sidecars-650w.webp 650w, /assets/images/139.6.techorama-sidecars-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/139.6.techorama-sidecars-960w.webp\" width=\"960\" height=\"1192\" alt=\"Techorama sidedar photos\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I did notice that for some people it felt a bit too awkward to sit in the sidecar to have their picture taken. So, on the second day, I decided to leave the sidecar as decoration and have attendees play the Dapr game instead. This worked much better to attract people to the booth, and a lot of people played the game! 🕹️</p><p>This year my colleague Alice joined me at the booth and this made the conference much more enjoyable and easier compared to last year when I was by myself! Thanks Alice! 🙏</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/139.2.techorama-440w.webp 440w, /assets/images/139.2.techorama-650w.webp 650w, /assets/images/139.2.techorama-960w.webp 960w, /assets/images/139.2.techorama-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/139.2.techorama-1200w.webp\" width=\"1200\" height=\"676\" alt=\"Techorama selfie\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/139.3.techorama-440w.webp 440w, /assets/images/139.3.techorama-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/139.3.techorama-650w.webp\" width=\"650\" height=\"756\" alt=\"Techorama selfie\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I had so much fun at this conference! I met so many former colleagues, and people I have worked with during my consultancy years. And I met a great deal of new(ish) people, even some who remembered me from previous year when Diagrid sponsored Techorama. I hope to see all of you again next year! 👋</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/139.4.techorama-440w.webp 440w, /assets/images/139.4.techorama-650w.webp 650w, /assets/images/139.4.techorama-960w.webp 960w, /assets/images/139.4.techorama-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/139.4.techorama-1200w.webp\" width=\"1200\" height=\"676\" alt=\"MVPs\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/139.5.techorama-440w.webp 440w, /assets/images/139.5.techorama-650w.webp 650w, /assets/images/139.5.techorama-960w.webp 960w, /assets/images/139.5.techorama-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/139.5.techorama-1200w.webp\" width=\"1200\" height=\"676\" alt=\"Playing the Dapr game\" loading=\"lazy\" decoding=\"async\"></picture></p>",
      "date_published": "2024-10-08T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-containerdays-2024/",
      "url": "https://marcduiker.dev/articles/speaking-at-containerdays-2024/",
      "title": "Speaking at ContainerDays 2024",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/138.1.containerdays-440w.webp 440w, /assets/images/138.1.containerdays-600w.webp 600w\" sizes=\"90vw\"><img src=\"/assets/images/138.1.containerdays-600w.webp\" width=\"600\" height=\"800\" alt=\"Arriving at ContainerDays\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" target=\"_blank\">Code demos on GitHub</a></p><hr><p>From September 3-4 I was in Germany to speak at the <a href=\"https://www.containerdays.io/containerdays-conference-2024/\" rel=\"noopener\">ContainerDays Conference</a>. I heard a lot of good stories about this conference but never had the opportunity to attend or speak, so I was thrilled to finally speak there!</p><p>My session was on the first day of the conference and was titled: <em>Start building distributed applications with ease using building block APIs</em>. In this session, I gave an introduction to the <a href=\"https://dapr.io/\" rel=\"noopener\">Dapr OSS</a> project, demonstrated the service invocation, state management, and pub/sub APIs, and showed how Dapr has built-in <a href=\"https://docs.dapr.io/operations/resiliency/policies/\" rel=\"noopener\">resiliency</a>.</p><p>All the code I demonstrated can be found in <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" rel=\"noopener\">this repo</a>. Check the <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution/tree/main/ResiliencyDemo\" rel=\"noopener\">ResiliencyDemo</a> folder and the corresponding CodeTour. The slides I used are part of the Dapr OSS slide deck which can be downloaded from the <a href=\"https://docs.dapr.io/contributing/presentations/\" rel=\"noopener\">Dapr docs</a>.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/138.2.containerdays-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/138.2.containerdays-440w.webp\" width=\"440\" height=\"782\" alt=\"Speaking at ContainerDays\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I had a great time at this conference, I had good conversations with Dapr enthusiasts (I hope to welcome you on the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a>!), and connected with the organizers of <a href=\"https://www.kcdmunich.de/\" rel=\"noopener\">KCD Munich</a>, I’d love to join you next year! During the speaker dinner I sat next to Raymond de Jong, who I never met before, but we lived in the same small Dutch city for years. It was great to meet you!</p><p>Thanks again to the organizers for accepting my talk, the lovely MCs, and the attendees for joining my session, it’s great to speak for a full room! I hope we meet again next year!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/138.3.containerdays-440w.webp 440w, /assets/images/138.3.containerdays-650w.webp 650w, /assets/images/138.3.containerdays-960w.webp 960w, /assets/images/138.3.containerdays-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/138.3.containerdays-1200w.webp\" width=\"1200\" height=\"676\" alt=\"Dapr stickers at ContainerDays\" loading=\"lazy\" decoding=\"async\"></picture></p>",
      "date_published": "2024-09-08T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-cphdevfest-2024/",
      "url": "https://marcduiker.dev/articles/speaking-at-cphdevfest-2024/",
      "title": "Speaking at Copenhagen Developers Festival 2024",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/137.1.cphdevfest-440w.webp 440w, /assets/images/137.1.cphdevfest-650w.webp 650w, /assets/images/137.1.cphdevfest-960w.webp 960w, /assets/images/137.1.cphdevfest-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/137.1.cphdevfest-1200w.webp\" width=\"1200\" height=\"1007\" alt=\"Speaking at CphDevFest\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" target=\"_blank\">Demo &amp; CodeTour on GitHub</a></p><hr><p>From August 28-30 I was in Denmark to speak at the <a href=\"https://cphdevfest.com/\" rel=\"noopener\">Copenhagen Developers Festival</a>, a conference organized by <a href=\"https://ndcconferences.com/\" rel=\"noopener\">NDC Conferences</a>. NDC always organizes excellent developer conferences and this was no exception. I really like the festival format where more creative aspects are included such as music, comedy, game shows, and using code for music and visuals (creative coding! 🤩).</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/137.2.cphdevfest-440w.webp 440w, /assets/images/137.2.cphdevfest-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/137.2.cphdevfest-650w.webp\" width=\"650\" height=\"866\" alt=\"Speaking at CphDevFest\" loading=\"lazy\" decoding=\"async\"></picture></p><p>My session was on the second day of the conference and was titled: <em>Failure is not an option: Durable Execution + Dapr = 🚀</em>. In this session, I demonstrated <a href=\"https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/\" rel=\"noopener\">Dapr Workflow</a>, various workflow patterns, and Dapr <a href=\"https://docs.dapr.io/operations/resiliency/policies/\" rel=\"noopener\">resiliency policies</a>.</p><p>All the code I demonstrated can be found in <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" rel=\"noopener\">this repo</a>.</p><p>There were quite some sessions about distributed applications or topics closely related to it. Layla Porter presented about the ‘modular monolith’ and Loek Duys had a session about Radius, where he referenced (and demo-ed) Dapr.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/137.3.cphdevfest-440w.webp 440w, /assets/images/137.3.cphdevfest-650w.webp 650w, /assets/images/137.3.cphdevfest-960w.webp 960w, /assets/images/137.3.cphdevfest-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/137.3.cphdevfest-1200w.webp\" width=\"1200\" height=\"676\" alt=\"Loek speaking about Radius and Dapr\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I had a great time at this conference, I had good conversations with some Dapr enthusiasts (I hope to welcome you on the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a>!), and as always it’s great to catch up with my peers (some are my former colleagues 🤗).</p><p>Thanks again to the program committee for accepting my talk, and thanks to the attendees for giving me a great rating! 🙏 I hope to be around next year again. Maybe even to perform some live <a href=\"/creative-coding\">creative coding</a>! 🤔</p>",
      "date_published": "2024-08-29T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/mcing-at-azure-lowlands-2024/",
      "url": "https://marcduiker.dev/articles/mcing-at-azure-lowlands-2024/",
      "title": "MC-ing at Azure &amp; AI Lowlands 2024",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/136.1.azurelowlands2024-440w.webp 440w, /assets/images/136.1.azurelowlands2024-650w.webp 650w, /assets/images/136.1.azurelowlands2024-960w.webp 960w, /assets/images/136.1.azurelowlands2024-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/136.1.azurelowlands2024-1200w.webp\" width=\"1200\" height=\"900\" alt=\"MC-ing at Azure Lowlands\" loading=\"lazy\" decoding=\"async\"></picture></p><hr><p>On June 27th I was at <a href=\"https://azurelowlands.com/\" rel=\"noopener\">Azure &amp; AI Lowlands</a>, Utrecht, The Netherlands. Not as a speaker this time, but I was MC-ing 🎤 in one of the rooms! I’m a big fan of the Azure Lowlands conferences, I love the venue <em>The Fabrique</em>, an old industrial area, and the conference always has a good mix of topics, diverse speakers, and plenty of entertainment. The theme of this year was <em>‘The 80s’</em> 🤩 and the opening act was a singing group of roller skaters, a skateboarder, and a BMX rider performing stunts on a half pipe! 🤯</p><p>I really enjoy MC-ing! I met many new speakers and listened to sessions which I probably wouldn’t have attended normally. Since I was MC-ing in the <em>Thriller</em> room, I presented a small fact about the <a href=\"https://en.wikipedia.org/wiki/Michael_Jackson%27s_Thriller_(music_video)\" rel=\"noopener\">Thriller music video</a> each time a new session started.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/136.5.azurelowlands2024-440w.webp 440w, /assets/images/136.5.azurelowlands2024-650w.webp 650w, /assets/images/136.5.azurelowlands2024-960w.webp 960w, /assets/images/136.5.azurelowlands2024-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/136.5.azurelowlands2024-1200w.webp\" width=\"1200\" height=\"798\" alt=\"Azure Lowlands speakers and org\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The organizers did a great job with the 80s theme. There were 80s characters, cars (KITT, Ecto-1, A-team van), and of course lots of 80s music. I had a great time at Azure &amp; AI Lowlands, and I’m looking forward to the next edition!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/136.4.azurelowlands2024-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/136.4.azurelowlands2024-440w.webp\" width=\"440\" height=\"664\" alt=\"Marc Duiker and ET at Azure Lowlands\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/136.3.azurelowlands2024-440w.webp 440w, /assets/images/136.3.azurelowlands2024-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/136.3.azurelowlands2024-650w.webp\" width=\"650\" height=\"787\" alt=\"KITT at Azure Lowlands\" loading=\"lazy\" decoding=\"async\"></picture></p>",
      "date_published": "2024-06-27T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-dotnet2024/",
      "url": "https://marcduiker.dev/articles/speaking-at-dotnet2024/",
      "title": "Speaking at dotnet2024 about Dapr Resiliency &amp; Durable Execution",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/135.1.dotnet2024-440w.webp 440w, /assets/images/135.1.dotnet2024-650w.webp 650w, /assets/images/135.1.dotnet2024-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/135.1.dotnet2024-960w.webp\" width=\"960\" height=\"720\" alt=\"Speaking at dotent2024\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR:</strong> <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" target=\"_blank\">Demo &amp; CodeTour (slides) on GitHub</a></p><hr><p>On June 25th, I spoke at the <a href=\"https://dotnetconfspain.com/\" rel=\"noopener\"><em>dotnet2024</em></a> conference in Madrid, Spain. My session was titled: <em>Failure is not an option: Durable Execution + Dapr = 🚀</em> and covered how <a href=\"https://dapr.io\" rel=\"noopener\">Dapr</a> has built-in features and APIs that helps developers building resilient distributed applications.</p><p>This was the first conference session where I only used VSCode for both slides and demos (no PowerPoint or Keynote). I’m using the <a href=\"https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour\" rel=\"noopener\">CodeTour extension</a> to drive the presentation. Please have a look at the <a href=\"https://github.com/diagrid-labs/dapr-resiliency-and-durable-execution\" rel=\"noopener\">GitHub repo</a> with the demos and slides, clone it locally and give CodeTour a try!</p><p>I finally got the opportunity to meet <a href=\"https://www.hanselman.com/\" rel=\"noopener\">Scott Hanselman</a> who gave a great keynote session at this conference! He really is an inspiration to me, he is very thoughful in everything he does, and such a skilled speaker.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/135.2.dotnet2024-440w.webp 440w, /assets/images/135.2.dotnet2024-650w.webp 650w, /assets/images/135.2.dotnet2024-960w.webp 960w, /assets/images/135.2.dotnet2024-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/135.2.dotnet2024-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Speaking at dotent2024\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Thanks to Unai Zorrilla Castra (<a href=\"https://www.plainconcepts.com/\" rel=\"noopener\">Plain Concepts</a>) and the other organizers of <em>dotnet2024</em> for inviting me, making me feel welcome, and creating such a good conference! It was great meeting with both old and new speaker friends there. I hope to come back another time!</p><hr><p>Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away! Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.3.dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/124.3.dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2024-06-25T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-futuretech-2024/",
      "url": "https://marcduiker.dev/articles/speaking-at-futuretech-2024/",
      "title": "Speaking at FutureTech 2024 about Dapr Actors",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/134.1.futuretech-440w.webp 440w, /assets/images/134.1.futuretech-650w.webp 650w, /assets/images/134.1.futuretech-960w.webp 960w, /assets/images/134.1.futuretech-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/134.1.futuretech-1200w.webp\" width=\"1200\" height=\"1200\" alt=\"Speaking at FutureTech\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR:</strong> <a href=\"/articles/2024/134.futuretech-dapr-actors.pdf\" target=\"_blank\">Download the slides</a> | <a href=\"https://github.com/diagrid-labs/dapr-actor-demos\" target=\"_blank\">Dapr Actor Demos on GitHub</a></p><hr><p>On April 17th, I spoke at the <a href=\"https://futuretech.nl\" rel=\"noopener\">FutureTech</a> conference in Utrecht, the Netherlands. I gave a session about Dapr Actors, one of the <a href=\"https://dapr.io\" rel=\"noopener\">Dapr</a> APIs that allows you to build distributed and concurrent applications using the Actor Model.</p><p>Next to my session, there were three other sessions that covered some aspects of Dapr:</p><ul class=\"list\"><li><em>Microservices Orchestration with Azure Container Apps</em> by Eduard Keilholz</li><li><em>DAPR and .NET Aspire: A royal wedding</em> by Florian van Dillen</li><li><em>Building Future-Ready Apps with .NET 8 and Azure Serverless Ecosystem</em> by Stas Lebedenko</li></ul><p>It was awesome to see so much Dapr content at a conference like FutureTech!</p><p>As usual, I brought plenty of Dapr stickers for the conference attendees. All of them were gone by the end of the day 😁.</p><p>Thanks to the organizers of FutureTech for inviting me and organizing this event! I really enjoyed meeting many former colleagues and friends again. The speaker dinner the evening before was an excellent opportunity to connect with peers and meet lovely new people.</p><hr><p>Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away! Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.3.dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/124.3.dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2024-04-17T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/dapr-game/",
      "url": "https://marcduiker.dev/articles/dapr-game/",
      "title": "Creating the Dapr game",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/133.2.dapr-game-384w.webp 384w\" sizes=\"90vw\"><img src=\"/assets/images/133.2.dapr-game-384w.webp\" width=\"384\" height=\"384\" alt=\"Dapr game\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I made another retro game! This time it’s a Dapr inspired game that I made for people new to Dapr. The game was first shown at KubeCon Paris 2024 at the Diagrid booth (see <a href=\"./132.kubecon-paris-2024.md\">this post</a>) but the game is now available for everyone to play online at <a href=\"https://marcduiker.itch.io/dapr-game\" rel=\"noopener\">itch.io</a>! 🎉</p><iframe frameborder=\"0\" src=\"https://itch.io/embed/2163754\" width=\"552\" height=\"167\">&lt;a href=\"https://marcduiker.itch.io/dapr-game\"&gt;The Dapr Game by marcduiker&lt;/a&gt;</iframe><p>The player needs to avoid rogue messages, collect Dapr coins and the Dapr hat, in order to get to the development team and share their Dapr knowledge. The game has an educational element because after a level is completed some Dapr information is shared with the player.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/133.4.dapr-game-sprite-384w.webp 384w\" sizes=\"90vw\"><img src=\"/assets/images/133.4.dapr-game-sprite-384w.webp\" width=\"384\" height=\"384\" alt=\"Dapr game sprite\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I made the game with <a href=\"https://www.lexaloffle.com/pico-8.php\" rel=\"noopener\">Pico8</a>, a fantasy game console for making, sharing, and playing retro games. I had a lot of fun drawing all the sprites, like the computers and the groups of developers, designing the sound effects, and composing the music. The gameplay is very similar to the Azure Functions game, but I added some new gameplay features, such as the gray/blue powerlines to limit player movement once the Dapr hat is collected. At the moment the game consists of 3 levels, but I will add a couple more over time.</p><p>If you have the Pico8 software, you can download this png file below and play the game offline.</p><blockquote><p>Note: The links at the end of the game won’t open in a browser when playing in Pico8 offline. That functionality is only supported in the web version available on <a href=\"http://itch.io\" rel=\"noopener\">itch.io</a>.</p></blockquote><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/133.3.pico8-dapr-cart-440w.webp 440w, /assets/images/133.3.pico8-dapr-cart-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/133.3.pico8-dapr-cart-650w.webp\" width=\"650\" height=\"832\" alt=\"Dapr game Pico8 cart\" loading=\"lazy\" decoding=\"async\"></picture></p>",
      "date_published": "2024-03-24T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/kubecon-paris-2024/",
      "url": "https://marcduiker.dev/articles/kubecon-paris-2024/",
      "title": "KubeCon Paris 2024",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/132.1.kubecon-paris-440w.webp 440w, /assets/images/132.1.kubecon-paris-650w.webp 650w, /assets/images/132.1.kubecon-paris-960w.webp 960w, /assets/images/132.1.kubecon-paris-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/132.1.kubecon-paris-1200w.webp\" width=\"1200\" height=\"799\" alt=\"KubeCon Paris Diagrid Team\" loading=\"lazy\" decoding=\"async\"></picture></p><hr><p>From March 19-22 KubeCon Europe was taking place in Paris. Diagrid was sponsoring both <a href=\"https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/co-located-events/appdevelopercon/\" rel=\"noopener\">AppDeveloperCon</a> and <a href=\"https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/\" rel=\"noopener\">KubeCon</a>, and I was responsible for the booths, swag, a Dapr demo to highlight a new free edition of <a href=\"https://www.diagrid.io/conductor\" rel=\"noopener\">one of our products</a>, and a <a href=\"./133.dapr-game.md\">retro game</a> to attract people to the booth.</p><p>A week before KubeCon I recorded a video to announce our presence at the event:</p><!-- ## TODO --><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/WlkThEEW10s?si=LEfK1ebCdwd_R3ii\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe><p>My colleagues did a ton of talks at both the co-located event and KubeCon, and I had the opportunity to give a lightning talk about Dapr. It was my first time speaking at KubeCon, so I was quite excited for this!</p><iframe width=\"326\" height=\"580\" src=\"https://www.youtube.com/embed/Hz_xsEHPx9c\" title=\"KubeCon Paris 2024\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/132.5.kubecon-paris-440w.webp 440w, /assets/images/132.5.kubecon-paris-650w.webp 650w, /assets/images/132.5.kubecon-paris-960w.webp 960w, /assets/images/132.5.kubecon-paris-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/132.5.kubecon-paris-1200w.webp\" width=\"1200\" height=\"817\" alt=\"KubeCon Dapr game\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I had a great time at the booth, talking to loads of people about Dapr and the Diagrid products. Many attendees (and other sponsors) played the Dapr retro game, took the pixelart stickers, or the multi tool, our give-away, that symbolizes Dapr and all of its APIs.</p><iframe width=\"326\" height=\"580\" src=\"https://www.youtube.com/embed/hAP91F3ZyqE\" title=\"KubeCon Paris 2024 - Playing the Dapr game\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe><p>It was great seeing my colleagues again, and meeting with many Dapr enthusiasts &amp; end-users IRL for the first time! 🎉</p><p>I’m really exhausted after 4 conference days, but I’m also very happy with the result. Now, I’ll go to sleep for two days 😴.</p>",
      "date_published": "2024-03-23T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/dapr-day-2024/",
      "url": "https://marcduiker.dev/articles/dapr-day-2024/",
      "title": "Organizing Dapr Day 2024",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/131.1.dapr-day-2024-banner-440w.webp 440w, /assets/images/131.1.dapr-day-2024-banner-650w.webp 650w, /assets/images/131.1.dapr-day-2024-banner-960w.webp 960w, /assets/images/131.1.dapr-day-2024-banner-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/131.1.dapr-day-2024-banner-1200w.webp\" width=\"1200\" height=\"304\" alt=\"Dapr Day 2024\" loading=\"lazy\" decoding=\"async\"></picture></p><hr><p>On Feb 21st Cecil Phillip and I MC-ed Dapr Day 2024, an online conference to learn all about the Dapr APIs for building distributed applications and best practices to run your apps in production.</p><p>Yaron Schneider, one of the Dapr co-creaters opened the event, followed by sessions from:</p><ul class=\"list\"><li>Alex Mang - Lessons Learnt From Running Dapr-based Apps In Production</li><li>Robin Konrad - .NET Aspire &amp; Dapr</li><li>Miroslav Janeski - Dapr Unleashed: Accelerating Microservice Development</li><li>Irvi Aini - Tracing Simplified: An Introduction to Microservices Observability with Dapr</li><li>Michiel van Praat - Building fintech solutions with Dapr</li><li>Gilles Flisch &amp; Lior Nabat - Elia’s Strategy for Streamlining Superior .NET 8 Microservices Application Development with Dapr and KubeMQ</li><li>Mika Krooswijk &amp; Stijn Rutten - Implementing Dapr in an existing environment</li><li>Kendall Roden - Dapr integrated: A tour of tools, platforms and partnerships backed by Dapr!</li></ul><p>You can watch all the sessions in this <a href=\"https://youtube.com/playlist?list=PLcip_LgkYwzvXxnvC4r1dax2ro-_OrpPy&amp;feature=shared\" rel=\"noopener\">playlist</a> on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/videoseries?si=C4S-iBiKQa4yi36p&amp;list=PLcip_LgkYwzvXxnvC4r1dax2ro-_OrpPy\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe><p>Next to MC-ing the event, I also was responsible for organizing the event, assembling a program committee, reviewing sessions, communicating with the speakers, and video editing. Thanks to the CNCF for using their platform for the CfPs and streaming the event! 🙏</p><p>I’m looking forward to the next Dapr Day! 😁</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/131.2.dapr-day-2024-banner-440w.webp 440w, /assets/images/131.2.dapr-day-2024-banner-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/131.2.dapr-day-2024-banner-650w.webp\" width=\"650\" height=\"165\" alt=\"Dapr Day 2024 animation\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away! Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.3.dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/124.3.dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2024-02-21T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-swetugg-stockholm-2024/",
      "url": "https://marcduiker.dev/articles/speaking-at-swetugg-stockholm-2024/",
      "title": "Speaking at Swetugg Stockholm 2024",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/126.1.swetugg-440w.webp 440w, /assets/images/126.1.swetugg-650w.webp 650w, /assets/images/126.1.swetugg-960w.webp 960w, /assets/images/126.1.swetugg-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/126.1.swetugg-1200w.webp\" width=\"1200\" height=\"903\" alt=\"Speaking at Swetugg\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR:</strong> <a href=\"/articles/2024/126.swetugg-serverless-dapr.pdf\" target=\"_blank\">Download the slides</a> | <a href=\"https://github.com/diagrid-labs/catalyst-pizza-demo\" target=\"_blank\">Catalyst Pizza Demo on GitHub</a></p><hr><p>This week I’m at <a href=\"https://www.swetugg.se/sthlm-2024\" rel=\"noopener\">Swetugg Stockholm</a> where I just gave my session <em>“One API to rule them all: serverless Dapr”</em>. This is a brand-new session, where I talked about the various Dapr deployment models, and demonstrated <a href=\"https://www.diagrid.io/catalyst\" rel=\"noopener\">Diagrid Catalyst</a>, a new product that offers true serverless Dapr APIs for communication, data, and workflow. Thanks to everyone who attended my session, the room was fully packed!</p><p>As usual, I brought plenty of Dapr stickers for the attendees, and I also brought some Dutch stroopwafels to share with the other speakers.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/126.2.swetugg-440w.webp 440w, /assets/images/126.2.swetugg-650w.webp 650w, /assets/images/126.2.swetugg-960w.webp 960w, /assets/images/126.2.swetugg-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/126.2.swetugg-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Stickers and stroopwafels\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Shout-out to the Swetugg organizers, they put together a great program, a lovely speaker dinner, and put a lot of effort in creating a relaxing environment for their speakers 🙏.</p><p>If you want to know more about Diagrid Catalyst, please visit the <a href=\"https://www.diagrid.io/catalyst\" rel=\"noopener\">Diagrid website</a>. You can sign up for the early access program there. I’d love go get your feedback on the product!</p>",
      "date_published": "2024-02-07T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azure-community-enthusiasts-5-feb/",
      "url": "https://marcduiker.dev/articles/azure-community-enthusiasts-5-feb/",
      "title": "Azure Community Enthusiasts: Dapr",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/129.1.azure-community-enthusiasts-440w.webp 440w, /assets/images/129.1.azure-community-enthusiasts-650w.webp 650w, /assets/images/129.1.azure-community-enthusiasts-960w.webp 960w, /assets/images/129.1.azure-community-enthusiasts-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/129.1.azure-community-enthusiasts-1200w.webp\" width=\"1200\" height=\"677\" alt=\"Azure Community Enthusiasts\" loading=\"lazy\" decoding=\"async\"></picture></p><hr><p>On Feb 5th I was a guest speaker at Azure Community Enthusiasts, a bi-weekly live show on <a href=\"https://www.youtube.com/@AzureCommUG\" rel=\"noopener\">YouTube</a> about anything related to the Azure cloud. I gave a session about Dapr, the Distributed Application Runtime. The other guest was Will Velida, who gave an introduction to Radius.</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/vV9R3owCdcQ\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><p>Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away! Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.3.dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/124.3.dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2024-02-05T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/festive-tech-calendar-2023/",
      "url": "https://marcduiker.dev/articles/festive-tech-calendar-2023/",
      "title": "Participating in Festive Tech Calendar 2023",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/127.1.festive-tech-calendar-440w.webp 440w, /assets/images/127.1.festive-tech-calendar-650w.webp 650w, /assets/images/127.1.festive-tech-calendar-960w.webp 960w, /assets/images/127.1.festive-tech-calendar-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/127.1.festive-tech-calendar-1200w.webp\" width=\"1200\" height=\"673\" alt=\"Festive Tech Calendar\" loading=\"lazy\" decoding=\"async\"></picture></p><hr><p>I’m a big fan of community driven tech events, and <a href=\"https://festivetechcalendar.com/\" rel=\"noopener\">Festive Tech Calendar</a> is one of them. It’s a virtual advent calendar for the tech community, where each day in December new content is released that is created by the community. This year I submitted a session about creating a workflow with Dapr and .NET, and made it in a Christmas theme. The session is called <em>“How Santa orchestrates the elves with Dapr Workflow”</em> and covers workflow application patterns such as chaining, fan-out/fan-in, and child workflows.</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/UjGMmY8D9sw\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><p>The Santa Claus Workflow demo is available on <a href=\"https://github.com/diagrid-labs/santa-claus-workflow\" rel=\"noopener\">GitHub</a>.</p><p>A big shout-out goes to the <a href=\"https://twitter.com/_cloudfamily\" rel=\"noopener\">organizers</a> as they always combine this event with a charity 💪.</p><p>Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away! Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.3.dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/124.3.dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2023-12-20T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-cloudbrew-2023/",
      "url": "https://marcduiker.dev/articles/speaking-at-cloudbrew-2023/",
      "title": "Speaking at Cloudbrew 2023",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/125.1.cloudbrew-440w.webp 440w, /assets/images/125.1.cloudbrew-650w.webp 650w, /assets/images/125.1.cloudbrew-960w.webp 960w, /assets/images/125.1.cloudbrew-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/125.1.cloudbrew-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Speaking at Cloudbrew\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR:</strong> <a href=\"/articles/2023/125.cloudbrew-dapr-actors.pdf\" target=\"_blank\">Download the slides</a> | <a href=\"https://github.com/diagrid-labs/dapr-actor-demos\" target=\"_blank\">Dapr actor demos on GitHub</a></p><hr><p>Last Thursday and Friday I was in Mechelen (BE) for <a href=\"https://www.cloudbrew.be/\" rel=\"noopener\">Cloudbrew 2023</a>. I gave my session <em>“Lights, camera, action! Building distributed applications with Dapr Actors”</em> on Friday in the auditorium.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/125.2.cloudbrew-440w.webp 440w, /assets/images/125.2.cloudbrew-650w.webp 650w, /assets/images/125.2.cloudbrew-960w.webp 960w, /assets/images/125.2.cloudbrew-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/125.2.cloudbrew-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Speaking at Cloudbrew\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>(Top two pictures by Andre van den Berg)</em></p><p>It was my second time at CloudBrew, and I really enjoy this mid-size conference held in Lamot conference center, a former beer brewery 🍺. Kudos to the crew for organizing such a welcoming event. I look forward to the next edition!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/125.3.cloudbrew-440w.webp 440w, /assets/images/125.3.cloudbrew-650w.webp 650w, /assets/images/125.3.cloudbrew-960w.webp 960w, /assets/images/125.3.cloudbrew-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/125.3.cloudbrew-1200w.webp\" width=\"1200\" height=\"1600\" alt=\"Selfie with hat\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/125.4.cloudbrew-440w.webp 440w, /assets/images/125.4.cloudbrew-650w.webp 650w, /assets/images/125.4.cloudbrew-960w.webp 960w, /assets/images/125.4.cloudbrew-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/125.4.cloudbrew-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Dapr stickers\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The slides can be <a href=\"/articles/2023/125.cloudbrew-dapr-actors.pdf\" target=\"_blank\">downloaded here</a>. The Dapr actor demos can be found in <a href=\"https://github.com/diagrid-labs/dapr-actor-demos\" rel=\"noopener\">this GitHub repo</a>.</p><p>A special shout-out goes to <a href=\"https://twitter.com/iamalexmang\" rel=\"noopener\">Alex Mang</a> for giving a great introduction to Dapr by giving examples how it speeds up microservice development 🚀.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/125.5.cloudbrew-alexmang-440w.webp 440w, /assets/images/125.5.cloudbrew-alexmang-650w.webp 650w, /assets/images/125.5.cloudbrew-alexmang-960w.webp 960w, /assets/images/125.5.cloudbrew-alexmang-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/125.5.cloudbrew-alexmang-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Alex Mang at CloudBrew\" loading=\"lazy\" decoding=\"async\"></picture></p><hr><p>Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away! Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.3.dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/124.3.dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2023-12-09T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-buildstuff-2023/",
      "url": "https://marcduiker.dev/articles/speaking-at-buildstuff-2023/",
      "title": "Speaking at BuildStuff 2023",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.1.buildstuff-440w.webp 440w, /assets/images/124.1.buildstuff-650w.webp 650w, /assets/images/124.1.buildstuff-960w.webp 960w, /assets/images/124.1.buildstuff-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/124.1.buildstuff-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Speaking at BuildStuff\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR:</strong> <a href=\"/assets/blog/images/2023/124.dapr-workflow-buildstuff.pdf\" target=\"_blank\">Download the slides</a> | <a href=\"https://github.com/diagrid-labs/dapr-workflow-demos\" target=\"_blank\">Dapr workflow demos on GitHub</a></p><hr><p>Last Thursday and Friday I was in Vilnius Lithuania for <a href=\"https://www.buildstuff.events/\" rel=\"noopener\">BuildStuff 2023</a>. I gave my session <em>“Orchestrating your business logic reliably with workflow as code”</em> on Friday. It was my first time at this conference (also my first time in Lithuania) and I really enjoyed it. The conference was very well organized, I loved the under water theme (with all the deep dive sessions 🤿), and I met with both old and new friends 🤗. Thanks everyone for attending my session, being patient when the projector failed 😅, and for asking great questions after the session!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.2.buildstuff-440w.webp 440w, /assets/images/124.2.buildstuff-650w.webp 650w, /assets/images/124.2.buildstuff-960w.webp 960w, /assets/images/124.2.buildstuff-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/124.2.buildstuff-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Marc with shark\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The slides can be <a href=\"/assets/blog/images/2023/124.dapr-workflow-buildstuff.pdf\" target=\"_blank\">downloaded here</a>. The Dapr workflow demos can be found in <a href=\"https://github.com/diagrid-labs/dapr-workflow-demos\" rel=\"noopener\">this GitHub repo</a>.</p><p>Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away! Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.3.dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/124.3.dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></p>",
      "date_published": "2023-11-18T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-swetugg-goteborg-2023/",
      "url": "https://marcduiker.dev/articles/speaking-at-swetugg-goteborg-2023/",
      "title": "Speaking at Swetugg Göteborg",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/123.1.swetugg-marcduiker-440w.webp 440w, /assets/images/123.1.swetugg-marcduiker-650w.webp 650w, /assets/images/123.1.swetugg-marcduiker-960w.webp 960w, /assets/images/123.1.swetugg-marcduiker-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/123.1.swetugg-marcduiker-1200w.webp\" width=\"1200\" height=\"799\" alt=\"Speaking at Swetugg\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR:</strong></p><ul class=\"list\"><li><a href=\"/articles/2023/123.swetugg-dapr-101.pdf\" target=\"_blank\">Download the Dapr introduction slides</a> | <a href=\"https://github.com/dapr/quickstarts\" target=\"_blank\">Dapr quickstarts on GitHub</a></li><li><a href=\"/articles/2023/123.swetugg-dapr-workflow.pdf\" target=\"_blank\">Download the Dapr workflow slides</a> | <a href=\"https://github.com/diagrid-labs/dapr-workflow-demos\" target=\"_blank\">Dapr workflow demos on GitHub</a></li></ul><hr><p>From Thursday 26th to Friday 27th October, I was at <a href=\"https://www.swetugg.se/gbg-2023\" rel=\"noopener\">Swetugg Göteborg</a> in Sweden. It was my first time at a Swetugg conference and I really enjoyed the experience.</p><p>I gave my session <em>“Start building applications with ease using building block APIs”</em> on Thursday. Since a couple of speakers had to cancel their sessions, I volunteered to give an additional talk, <em>“Orchestrating your business logic reliably with workflow as code”</em> on Friday. Thanks to everyone that attended!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/123.2.swetugg-marcduiker-440w.webp 440w, /assets/images/123.2.swetugg-marcduiker-650w.webp 650w, /assets/images/123.2.swetugg-marcduiker-960w.webp 960w, /assets/images/123.2.swetugg-marcduiker-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/123.2.swetugg-marcduiker-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Speaking at Swetugg\" loading=\"lazy\" decoding=\"async\"></picture></p><p>My Dapr intro slides can be <a href=\"/articles/2023/122.ndcporto-dapr-workflow.pdf\" target=\"_blank\">downloaded here</a>. The Dapr quickstarts that I demoed during the session can be found in <a href=\"https://github.com/dapr/quickstarts\" rel=\"noopener\">this GitHub repo</a>.</p><p>The Dapr workflow slides can be <a href=\"/articles/2023/122.ndcporto-dapr-workflow.pdf\" target=\"_blank\">downloaded here</a>. The Dapr workflow demos can be found in <a href=\"https://github.com/diagrid-labs/dapr-workflow-demos\" rel=\"noopener\">this GitHub repo</a>.</p><p>Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away!</p>",
      "date_published": "2023-10-27T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-ndcporto-2023/",
      "url": "https://marcduiker.dev/articles/speaking-at-ndcporto-2023/",
      "title": "Speaking at NDC Porto about Dapr workflow",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/122.ndc-porto-speaking-440w.webp 440w, /assets/images/122.ndc-porto-speaking-650w.webp 650w, /assets/images/122.ndc-porto-speaking-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/122.ndc-porto-speaking-960w.webp\" width=\"960\" height=\"626\" alt=\"Speaking at NDC Porto\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR: <a href=\"/articles/2023/122.ndcporto-dapr-workflow.pdf\" target=\"_blank\">Download the slides</a> | <a href=\"https://github.com/diagrid-labs/dapr-workflow-demos\" target=\"_blank\">Dapr workflow demos on GitHub</a></strong></p><p>From Wednesday 18th to Friday 20th October, I was at <a href=\"https://ndcporto.com/agenda\" rel=\"noopener\">NDC Porto</a> in Portugal. NDC organizes excellent developer conferences, and I was very happy my abstract <em>“orchestrating your business logic reliably with workflow as code”</em> was accepted 🥳.</p><p>By looking at the conference agenda, it was clear that microservices and (event-driven) architecture related talks are very popular currently, so my Dapr talk was a good fit 💪. I couldn’t attend many of the other talks, unfortunately, but I’ll watch them as soon as they’re published on <a href=\"https://www.youtube.com/@ndc\" rel=\"noopener\">YouTube</a>.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/122.1.ndc-porto-speaking-440w.webp 440w, /assets/images/122.1.ndc-porto-speaking-650w.webp 650w, /assets/images/122.1.ndc-porto-speaking-960w.webp 960w, /assets/images/122.1.ndc-porto-speaking-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/122.1.ndc-porto-speaking-1200w.webp\" width=\"1200\" height=\"798\" alt=\"Speaking at NDC Porto\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I’m glad my session was well attended, especially when someone like Richard Campbell is giving a talk at the same time 😅. Thanks to everyone for attending, and asking great questions afterwards. I hope to see you at another NDC conference again!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/122.2.ndc-porto-speaking-440w.webp 440w, /assets/images/122.2.ndc-porto-speaking-650w.webp 650w, /assets/images/122.2.ndc-porto-speaking-960w.webp 960w, /assets/images/122.2.ndc-porto-speaking-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/122.2.ndc-porto-speaking-1200w.webp\" width=\"1200\" height=\"798\" alt=\"Speaking at NDC Porto\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I really like meeting people at these conferences. I spoke to many developers who I haven’t met before and talked about Dapr (and pixelart). I also (re)connected with some former colleagues and speaker friends ❤️. It was great to meet you all! 🤗</p><p>My slides can be <a href=\"/articles/2023/122.ndcporto-dapr-workflow.pdf\" target=\"_blank\">downloaded here</a>. The source code that I showed during the session can be found in <a href=\"https://github.com/diagrid-labs/dapr-workflow-demos\" rel=\"noopener\">this GitHub repo</a>. Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away!</p>",
      "date_published": "2023-10-20T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/devopsdays-creative-coding/",
      "url": "https://marcduiker.dev/articles/devopsdays-creative-coding/",
      "title": "Stop adding business value and become an artist with your IT skills",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/119.1.devopsdays-eh-creativecoding-440w.webp 440w, /assets/images/119.1.devopsdays-eh-creativecoding-650w.webp 650w, /assets/images/119.1.devopsdays-eh-creativecoding-960w.webp 960w, /assets/images/119.1.devopsdays-eh-creativecoding-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/119.1.devopsdays-eh-creativecoding-1200w.webp\" width=\"1200\" height=\"900\" alt=\"DevOpsDays ignite session\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR: <a href=\"/assets/images/blog/2023/119.become-an-artist-with-your-it-skills.pdf\" target=\"_blank\">Download the slides</a> | <a href=\"https://www.youtube.com/c/TheCodingTrain\" target=\"_blank\">The Coding Train YouTube channel</a></strong></p><p>I recently got an invite to do an <a href=\"https://www.ignitetalks.io/\" rel=\"noopener\">ignite talk</a> at <a href=\"https://devopsdays.org/events/2023-eindhoven/welcome/\" rel=\"noopener\">DevOpsDays Eindhoven</a>. I never heard about the format, but it’s a 5 minute long presentation with 20 slides that auto advance. I never tried this before but, at times, I like to go out of my comfort zone, so I said yes 😬.</p><p>The topic I chose is one I love best: creative coding. I named named the session <em>“Stop adding business value and become and artist with your IT skills”</em>.<br>I was quite nervous giving this talk but I also had a lot of fun! Thanks everyone for attending my session!</p><p>My slides can be <a href=\"/assets/images/blog/2023/119.become-an-artist-with-your-it-skills.pdf\" target=\"_blank\">downloaded here</a>. A good starting point for learning <a href=\"https://p5js.org\" rel=\"noopener\">P5js</a> is <a href=\"https://www.youtube.com/c/TheCodingTrain\" rel=\"noopener\">The Coding Train</a> on YouTube. There is also an <a href=\"https://github.com/terkelg/awesome-creative-coding\" rel=\"noopener\">awesome list on GitHub</a> with links to many more creative coding resources to keep you busy for months!</p>",
      "date_published": "2023-10-12T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/4cee-meetup-dapr/",
      "url": "https://marcduiker.dev/articles/4cee-meetup-dapr/",
      "title": "Speaking at 4CEE Tech Talk about Dapr",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/120.1.4cee-marc-440w.webp 440w, /assets/images/120.1.4cee-marc-650w.webp 650w, /assets/images/120.1.4cee-marc-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/120.1.4cee-marc-960w.webp\" width=\"960\" height=\"638\" alt=\"4CEE tech talk\" loading=\"lazy\" decoding=\"async\"></picture></p><p><strong>TLDR: <a href=\"/articles/2023/120.c4ee-dapr-101.pdf\" target=\"_blank\">Download the slides</a> | <a href=\"https://github.com/dapr/quickstarts\" target=\"_blank\">Dapr Quickstarts repository</a></strong></p><p>Jeffrey Bosch invited me to give a tech talk at the 4CEE meetup in Ede on Thu 12th of Oct. It was a busy week with both <a href=\"/articles/sponsoring-techorama-nl-2023\">Techorama</a> and <a href=\"/articles/devopsdays-creative-coding\">DevOpsDays Eindhoven</a>, but I was glad I could join this meetup and show people the benefits of building applications with <a href=\"https://dapr.io\" rel=\"noopener\">Dapr</a>. The event was very well organized, the turn-out was great, and this was only the second time they organized such a thing! 👏</p><p>The other speaker at this meetup was Stacy Cashmore, who talked about creating a blog website using Blazor and Azure Static Web Apps. Stacy is a great speaker, I really enjoy her enthusiasm and the funny jokes she makes while doing live coding. 🚀</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/120.2.4cee-marc-440w.webp 440w, /assets/images/120.2.4cee-marc-650w.webp 650w, /assets/images/120.2.4cee-marc-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/120.2.4cee-marc-960w.webp\" width=\"960\" height=\"638\" alt=\"Marc close-up\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I gave an introductory session about Dapr, the distributed application runtime, and demonstrated service-to-service invocation, pub/sub messaging, and state management. Thanks everyone for attending and asking good questions!</p><p>My slides can be <a href=\"/articles/2023/120.c4ee-dapr-101.pdf\" target=\"_blank\">downloaded here</a>. A good starting point for learning Dapr is the <a href=\"https://github.com/dapr/quickstarts\" rel=\"noopener\">Dapr Quickstarts repository</a> on GitHub. Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away!</p>",
      "date_published": "2023-10-12T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/sponsoring-techorama-nl-2023/",
      "url": "https://marcduiker.dev/articles/sponsoring-techorama-nl-2023/",
      "title": "Sponsoring Techorama NL and (re-)connecting with the community ❤️",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/121.1.techorama-440w.webp 440w, /assets/images/121.1.techorama-650w.webp 650w, /assets/images/121.1.techorama-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/121.1.techorama-960w.webp\" width=\"960\" height=\"720\" alt=\"Selfie at the booth\" loading=\"lazy\" decoding=\"async\"></picture></p><p>On Tuesday and Wednesday this week (10 &amp; 11 Oct), I was at <a href=\"https://techorama.nl/\" rel=\"noopener\">Techorama</a> in Utrecht, The Netherlands. Techorama is a well-known conference in both the Netherlands and Belgium, and both editions attract a large number of developers. This time I wasn’t a speaker or attendee, but I was there as a sponsor representing <a href=\"https://diagrid.io\" rel=\"noopener\">Diagrid</a>.</p><p>Sponsoring this event was a great opportunity for Diagrid to increase the awareness around both Dapr and Diagrid, and I had a lot of fun at the booth talking to people about <a href=\"https://dapr.io\" rel=\"noopener\">Dapr</a>, the benefits of using it, as well as explaining Diagrid and the products we’re making.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/121.2.builders-440w.webp 440w, /assets/images/121.2.builders-650w.webp 650w, /assets/images/121.2.builders-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/121.2.builders-960w.webp\" width=\"960\" height=\"540\" alt=\"Lego builders\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Since I’m a big Lego fan myself, and I know many people in IT are as well, I decided to bring a box of Lego pieces, so attendees could build their own Lego microservice, connect it to other services, resulting in a large distributed application. This turned out to a big success, many developers stopped by to build a service (or two), and the result really looked like a (chaotic) Lego artwork. Thanks for building everyone! 🏗️</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/0fYHfPnEAyU\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/121.3.lego-440w.webp 440w, /assets/images/121.3.lego-650w.webp 650w, /assets/images/121.3.lego-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/121.3.lego-960w.webp\" width=\"960\" height=\"540\" alt=\"Lego top down view\" loading=\"lazy\" decoding=\"async\"></picture></p><p>At the end of the second day, I raffled off an Indiana Jones Lego kit, which was won by Jop. Many congratulations! 🎉</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/121.4.winner-197w.webp 197w\" sizes=\"90vw\"><img src=\"/assets/images/121.4.winner-197w.webp\" width=\"197\" height=\"350\" alt=\"Winner of the Lego set\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I’m really happy how this event turned out. I spoke to a lot of people about Dapr &amp; Diagrid, and reconnected with many old friends &amp; former colleagues. I finally met Paul Yuknewicz in-person (thank you for giving some great Dapr sessions 💪).</p><p>Special shout-outs go to Alex Thissen, Sander Molenkamp, and Edwin van Wijk for organizing a Dapr workshop on the first day of Techorama 👏. I spoke to many of the workshop attendees and they all enjoyed it and learned a lot.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/121.5.stickers-440w.webp 440w, /assets/images/121.5.stickers-650w.webp 650w, /assets/images/121.5.stickers-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/121.5.stickers-960w.webp\" width=\"960\" height=\"638\" alt=\"Dapr stickers\" loading=\"lazy\" decoding=\"async\"></picture></p><p>If you want to learn more about Dapr, take a look at the <a href=\"https://github.com/dapr/quickstarts\" rel=\"noopener\">Dapr Quickstarts repository</a> on GitHub. Do you have questions (or feedback) about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away!</p>",
      "date_published": "2023-10-11T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/dotnetfriday-dapr-workflow/",
      "url": "https://marcduiker.dev/articles/dotnetfriday-dapr-workflow/",
      "title": "Speaking at DotNetFriday about Dapr Workflow",
      "content_html": "<p><strong>TLDR: <a href=\"/assets/images/blog/2023/118.dotnetfriday-dapr-workflow.pdf\" target=\"_blank\">Download the slides</a> | <a href=\"https://github.com/diagrid-labs/dapr-workflow-demos\" target=\"_blank\">GitHub repo with workflow demos</a></strong></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/118.dotnetfriday-1-440w.webp 440w, /assets/images/118.dotnetfriday-1-650w.webp 650w, /assets/images/118.dotnetfriday-1-960w.webp 960w, /assets/images/118.dotnetfriday-1-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/118.dotnetfriday-1-1200w.webp\" width=\"1200\" height=\"903\" alt=\"DotNet Friday speaking\" loading=\"lazy\" decoding=\"async\"></picture></p><p><a href=\"https://www.linkedin.com/in/eduard-keilholz/\" rel=\"noopener\">Eduard Keilhotlz</a> invited me to talk at a <a href=\"https://dotnetfriday.nl/\" rel=\"noopener\">DotnetFriday</a> event many months ago, but there were always clashes with my agenda. Last Friday it finally happened, I went to the 4DotNet office in Nieuwegein and gave a talk about orchestrating your business logic with <a href=\"https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/\" rel=\"noopener\">Dapr workflow</a>.</p><p>When I arrived there I was welcomed with a cake, since earlier that week it was my birthday! 🎂 Thank you so much for this lovely surprise!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/118.dotnetfriday-3-440w.webp 440w, /assets/images/118.dotnetfriday-3-650w.webp 650w, /assets/images/118.dotnetfriday-3-960w.webp 960w, /assets/images/118.dotnetfriday-3-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/118.dotnetfriday-3-1200w.webp\" width=\"1200\" height=\"1600\" alt=\"DotNet Friday cake\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I finally met with <a href=\"https://www.linkedin.com/in/kenny-baas/\" rel=\"noopener\">Kenny Baas-Schwegler</a> again, who gave a session after me, together with <a href=\"https://www.linkedin.com/in/brunoboucard/\" rel=\"noopener\">Bruno Boucard</a>, about refactoring towards deeper insights using Domain-Driven Design. This was a great example of DDD in practise with live coding.</p><p>Thanks everyone for attending this event and asking great questions about Dapr workflow. My slides can be <a href=\"/articles/2023/118.dotnetfriday-dapr-workflow.pdf\" target=\"_blank\">downloaded here</a>. The demo apps can be found in this <a href=\"https://github.com/diagrid-labs/dapr-workflow-demos\" target=\"_blank\">GitHub repo</a>. If you want to learn more about Dapr or have questions about the project, please join the <a href=\"http://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a>.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/118.dotnetfriday-2-440w.webp 440w, /assets/images/118.dotnetfriday-2-650w.webp 650w, /assets/images/118.dotnetfriday-2-960w.webp 960w, /assets/images/118.dotnetfriday-2-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/118.dotnetfriday-2-1200w.webp\" width=\"1200\" height=\"800\" alt=\"DotNet Friday close-up\" loading=\"lazy\" decoding=\"async\"></picture></p>",
      "date_published": "2023-10-02T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/mc-ingmsbuild/",
      "url": "https://marcduiker.dev/articles/mc-ingmsbuild/",
      "title": "MC-ing at MSBuild The Netherlands",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/117.msbuild-nl-1-440w.webp 440w, /assets/images/117.msbuild-nl-1-650w.webp 650w, /assets/images/117.msbuild-nl-1-960w.webp 960w, /assets/images/117.msbuild-nl-1-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/117.msbuild-nl-1-1200w.webp\" width=\"1200\" height=\"812\" alt=\"MC-ing MSBuild\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Last week I had the privilege to MC at MSBuild the Netherlands with <a href=\"https://www.linkedin.com/in/stacycash/\" rel=\"noopener\">Stacy Cashmore</a>. This developer conference was the first local flagship developer conference organized by Microsoft in many years. The event was held at Media Plaza in Utrecht, the Netherlands.</p><p>Stacy and I were tasked with introducing the speakers, moderating the Q&amp;A, and ensuring everyone sticks to the agenda. It all went quite smooth until halfway through April Edwards keynote, when one of the camera crew fell pretty badly and had to be taken to the hospital. This was the first time I ever encountered a situation like this at a conference. It made me realize that event organizers should be prepared for literally anything. We shuffled the agenda a bit and were on track again pretty quick. Major kudos to April, who helped stabilize the injured cameraman, and continued her keynote after a break.</p><p>The day was packed with good sessions around Azure, GitHub, AI, and Security. It was great to see so many people in person again, both attendees and community partners. At the end of the day, we gave away some prizes to people that shared their MSBuild selfie online (I wish I had won the MS Build branded Xbox!). I really enjoyed this day, and it was a great opportunity to wear my Dapr hat! 😁</p><p>For those of you that missed the in person event, you can watch the recordings of the sessions soon on the <a href=\"https://pulse.microsoft.com/nl-nl/microsoft-build-nl/\" rel=\"noopener\">Microsoft website</a>.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/117.msbuild-nl-2-440w.webp 440w, /assets/images/117.msbuild-nl-2-650w.webp 650w, /assets/images/117.msbuild-nl-2-960w.webp 960w, /assets/images/117.msbuild-nl-2-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/117.msbuild-nl-2-1200w.webp\" width=\"1200\" height=\"900\" alt=\"MSBuild org\" loading=\"lazy\" decoding=\"async\"></picture></p>",
      "date_published": "2023-10-01T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaking-at-tweakers-dev-summit/",
      "url": "https://marcduiker.dev/articles/speaking-at-tweakers-dev-summit/",
      "title": "Speaking at the Tweakers Developers Summit 2023",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/116.tweakers-dev-summit-marcduiker-440w.webp 440w, /assets/images/116.tweakers-dev-summit-marcduiker-650w.webp 650w, /assets/images/116.tweakers-dev-summit-marcduiker-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/116.tweakers-dev-summit-marcduiker-960w.webp\" width=\"960\" height=\"640\" alt=\"Speaking at Tweakers Dev Summit (Photo by Tweakers)\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Last week I had the oppurtunity to speak at the <a href=\"https://tweakers.net/partners/devsummit2023/1900/sprekerstracks/\" rel=\"noopener\">Tweakers Developers Summit</a> in Utrecht, the Netherlands.</p><p>The event consisted of several tracks including, front-end, back-end, DevOps, security, smart home, and AI. The venue was the awesome De Fabrique (old industrial complex) and it was packed with developers. I really enjoyed watching the keynote by <a href=\"http://lindaliukas.com/\" rel=\"noopener\">Linda Liukas</a>, the author of the <a href=\"http://www.helloruby.com/\" rel=\"noopener\">Hello Ruby</a> book series. It was really inspiring to hear her talk about how children (and adults!) can learn programming concepts in a playful way.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/116.tweakers-dev-summit-440w.webp 440w, /assets/images/116.tweakers-dev-summit-650w.webp 650w, /assets/images/116.tweakers-dev-summit-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/116.tweakers-dev-summit-960w.webp\" width=\"960\" height=\"540\" alt=\"My Tweakers Developer Summit access card\" loading=\"lazy\" decoding=\"async\"></picture></p><p>My session, which was part of the back-end track, covered how <a href=\"https://dapr.io\" rel=\"noopener\">Dapr</a> speeds up distributed systems development by using the many building block APIs. The preceding session named <em>From Monolith to Microservices and Beyond</em> was a great lead-in to my session (thanks again Peter van Vliet!).</p><p>The slides can be <a href=\"/assets/images/blog/2023/116.tweakers-dev-summit-presentation.pdf\" target=\"_blank\">downloaded here</a>. If you want to learn more about Dapr or have questions about the project, please join the <a href=\"http://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a>.</p>",
      "date_published": "2023-09-17T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/devrelcon-london-2023/",
      "url": "https://marcduiker.dev/articles/devrelcon-london-2023/",
      "title": "Attending DevRelCon London 2023",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/115.devrelcon_selfie-440w.webp 440w, /assets/images/115.devrelcon_selfie-650w.webp 650w, /assets/images/115.devrelcon_selfie-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/115.devrelcon_selfie-960w.webp\" width=\"960\" height=\"720\" alt=\"DevRelCon London selfie with the logo\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The first <a href=\"https://developerrelations.com/devrelcon\" rel=\"noopener\">DevRelCon</a> I attended was in 2022 in Prague, where I had the opportunity to speak about how to use your non-IT skills to build your personal brand (perhaps some day the recording will be published 😅).</p><p>I had a superb time there, it really felt I was among my people ❤️. So when I noticed that the 2023 edition would be in London, I immediately bought a ticket.</p><h2 id=\"creating-the-logo\"><a href=\"#creating-the-logo\" class=\"heading-anchor\">Creating the logo</a></h2><p>Not long after the 2022 conference, Matthew Revell, the conference organizer, approached me and asked if I would like to create the logo for the London conference. Of course, I immediately said yes to this!<br>Many hours of drawing followed, and I’m very happy with the result.</p><p>Here’s the time-lapse of the creation of the logo:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/kCJT1eoR8U8?si=P3LJ4dYmAI959z3o\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe><h2 id=\"the-conference\"><a href=\"#the-conference\" class=\"heading-anchor\">The conference</a></h2><p>After a smooth Eurostar travel from Rotterdam to London, I went to the pre-conference drinks, where I met some of my lovely former colleagues and many new folks.<br>There was a surprising large Dutch devrel delegation, some I’ve never met before 😲 (Thanks Julien for bringing the stroopwafels!).</p><p>The main reason I went to this conference is to (re)connect with the wonderful people in Developer Relations.<br>Don’t get me wrong, the talks are good. I’ve learned quite a bit about:</p><ul class=\"list\"><li>Developer education (Sue Smith)</li><li>Cracking complext community problems (Richard Millington)</li><li>Communication design (Melinda Seckington)</li><li>Quantifying devrel impact (Kevin Lewis and John Booth).</li></ul><p>There were many more talks, and a devrel unconference at the same time. It was too much to follow everything 🤯.<br>As our industry is relatively young and not well-defined, I’m very appreciative of everyone who is sharing their knowledge and experience. You are helping us all to grow up (this is a hot topic lately).</p><p>I got a lot of good energy from attending this conference and meeting everyone in person 🤗. I hope a new edition of DevRelCon will happen in 2024. I will be there for sure!</p><p><em>(Also…<a href=\"https://ko-fi.com/marcduiker/commissions\" rel=\"noopener\">let me know</a> if you need a pixel-art style logo for your conference 😉)</em></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/115.2.devrelcon_opening-440w.webp 440w, /assets/images/115.2.devrelcon_opening-650w.webp 650w, /assets/images/115.2.devrelcon_opening-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/115.2.devrelcon_opening-960w.webp\" width=\"960\" height=\"540\" alt=\"DevRelCon opening by Matthew and Kevin\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/115.3.devrelcon_wesley-440w.webp 440w, /assets/images/115.3.devrelcon_wesley-650w.webp 650w, /assets/images/115.3.devrelcon_wesley-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/115.3.devrelcon_wesley-960w.webp\" width=\"960\" height=\"540\" alt=\"DevRelCon keynote by Wesley Faulkner\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/115.4.devrelcon_sue-440w.webp 440w, /assets/images/115.4.devrelcon_sue-650w.webp 650w, /assets/images/115.4.devrelcon_sue-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/115.4.devrelcon_sue-960w.webp\" width=\"960\" height=\"540\" alt=\"Sue Smith: An open framework for developer learning\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/115.5.devrelcon_kevin-440w.webp 440w, /assets/images/115.5.devrelcon_kevin-650w.webp 650w, /assets/images/115.5.devrelcon_kevin-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/115.5.devrelcon_kevin-960w.webp\" width=\"960\" height=\"540\" alt=\"Kevin Lewis &amp; John Booth: Learning From Marketing to Quantify Developer Relations Impact\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/115.6.devrelcon_melinda-440w.webp 440w, /assets/images/115.6.devrelcon_melinda-650w.webp 650w, /assets/images/115.6.devrelcon_melinda-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/115.6.devrelcon_melinda-960w.webp\" width=\"960\" height=\"540\" alt=\"Melinda Seckington: The Art of Communication Design\" loading=\"lazy\" decoding=\"async\"></picture></p>",
      "date_published": "2023-09-14T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/live-stream-gregor-suttie/",
      "url": "https://marcduiker.dev/articles/live-stream-gregor-suttie/",
      "title": "Live stream with Gregor Suttie",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/130.1.gregory-suttie-stream-440w.webp 440w, /assets/images/130.1.gregory-suttie-stream-650w.webp 650w, /assets/images/130.1.gregory-suttie-stream-960w.webp 960w, /assets/images/130.1.gregory-suttie-stream-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/130.1.gregory-suttie-stream-1200w.webp\" width=\"1200\" height=\"679\" alt=\"Live Stream with Gregory Suttie\" loading=\"lazy\" decoding=\"async\"></picture></p><hr><p>On July 18th I joined a live stream hosted by <a href=\"https://www.youtube.com/@GregorSuttie\" rel=\"noopener\">Gregor Suttie</a>, an Azure MVP and active community speaker and organizer. We talked about Dapr and I demonstrated the State Management API, resiliency, and Dapr Worflow.</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/82qRiLAihXU\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><p>Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away! Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.3.dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/124.3.dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2023-07-18T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/cloud-native-weekly-ep5/",
      "url": "https://marcduiker.dev/articles/cloud-native-weekly-ep5/",
      "title": "Cloud Native Weekly Episode 5: Dapr",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/128.1.cloud-native-weekly-440w.webp 440w, /assets/images/128.1.cloud-native-weekly-650w.webp 650w, /assets/images/128.1.cloud-native-weekly-960w.webp 960w, /assets/images/128.1.cloud-native-weekly-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/128.1.cloud-native-weekly-1200w.webp\" width=\"1200\" height=\"678\" alt=\"Cloud Native Weekly\" loading=\"lazy\" decoding=\"async\"></picture></p><hr><p>On July 6th I was a guest speaker at Cloud Native Weekly, a weekly live show on <a href=\"https://www.youtube.com/@CloudNativeWeekly\" rel=\"noopener\">YouTube</a> where the hosts and guests discuss the latest news and trends in the cloud native space. This episode was all about Dapr, the Distributed Application Runtime.</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/Zcdsh6J5fAg\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><p>Do you have questions about Dapr? Please join the <a href=\"https://bit.ly/dapr-discord\" rel=\"noopener\">Dapr Discord</a> and ask away! Do you like Dapr and want to show your support? Claim this <a href=\"https://bit.ly/dapr-supporter\" rel=\"noopener\">community supporter Holopin badge</a>!</p><p><a href=\"https://bit.ly/dapr-supporter\"><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/124.3.dapr-community-supporter-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/124.3.dapr-community-supporter-440w.webp\" width=\"440\" height=\"440\" alt=\"Dapr community supporter badge\" loading=\"lazy\" decoding=\"async\"></picture></a></p>",
      "date_published": "2023-07-06T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/speaker-buddy/",
      "url": "https://marcduiker.dev/articles/speaker-buddy/",
      "title": "Be ready for failure on stage: introducing the Speaker Buddy System",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/64.speaker_buddy_dall-e-440w.webp 440w, /assets/images/64.speaker_buddy_dall-e-650w.webp 650w, /assets/images/64.speaker_buddy_dall-e-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/64.speaker_buddy_dall-e-960w.webp\" width=\"960\" height=\"960\" alt=\"Speaker Buddy Generated by DALL-E\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"conferences\"><a href=\"#conferences\" class=\"heading-anchor\">Conferences</a></h2><p>I love ❤️ going to conferences, especially when I’m invited to speak there. Although I’ve been speaking at dozens of conferences and meetups over the past years, I still have impostor syndrome when it comes to speaking. Some people really seem to have a gift for public speaking, and sometimes I feel way out of my league. I do realize those experienced speakers have been practicing for years, and perhaps I’ll get there as well, practice makes <s>perfect</s> improvement! 💪</p><h2 id=\"failing-on-stage\"><a href=\"#failing-on-stage\" class=\"heading-anchor\">Failing on stage</a></h2><p>One thing that can throw me off during my speaking sessions is technical issues. And I don’t mean failing demos (I’m used to those 🫠), but audio/video issues that require minutes (feels like hours 😭) to solve.</p><p>A couple of weeks ago, I had such an issue where my laptop didn’t mirror the screen to the AV system. I was using a MacBook that I had used a dozen times before, but I’m not very familiar with macOS, and even when I got some help (and tried multiple adapters), the issue wasn’t solved. It was a few minutes past the official start time of the talk, and I was now getting nervous (and I’m quite a chill person otherwise). Luckily the previous speaker (<a href=\"https://github.com/devlead\" rel=\"noopener\">Mattias Karlsson</a>) had a brilliant idea: he asked me to install TeamViewer on my laptop, connect to the TeamViewer session on his laptop, and he would connect his machine to the AV system. This worked like a charm (WiFi was reasonable), and I was able to start my talk about 5 minutes late. 🎉 The stress I built up during those minutes was not beneficial for my delivery though. My breathing was shallow, and my pace was too fast.</p><p>Could I have prevented the technical issue? No, but I could have been better prepared.</p><blockquote><p>TLDR: Have a look at the <a href=\"https://github.com/marcduiker/speaker-buddy\" rel=\"noopener\">Speaker Buddy System</a>.</p></blockquote><h2 id=\"preparing-for-failure\"><a href=\"#preparing-for-failure\" class=\"heading-anchor\">Preparing for failure</a></h2><p>There are several ways of preparing for technical issues. I’ll share some of my ideas, and close with an idea initially proposed by Mattias, called the Speaker Buddy System.</p><p>I won’t cover ‘my demo doesn’t work’ failures here, since these things can be solved in different ways, such as; showing a solution that has been prepared beforehand, or recording the solution before the conference and playing it back during the session.</p><p>The general idea to overcome a catastrophic (laptop) failure is to ensure that your presentation, code, and demos are available on other environments you can quickly access.</p><p>Let’s go through some solutions:</p><h3 id=\"bring-a-second-laptop\"><a href=\"#bring-a-second-laptop\" class=\"heading-anchor\">Bring a second laptop</a></h3><p>Yes, it can be that ‘easy’. But one, you need to <em>have</em> a second laptop (💸), and two, you need to take it with you when traveling, which isn’t always an option. I have a small Windows Surface Go that I usually bring with me next to my main laptop.</p><h3 id=\"cloud-storage\"><a href=\"#cloud-storage\" class=\"heading-anchor\">Cloud storage</a></h3><p>For things that don’t require showing or running code, you can use a cloud storage solution like OneDrive, Google Drive, Dropbox, or even a git repository. I use OneDrive to store and access my presentations. If your laptop fails, you still need to use another machine, but you can access your presentation relatively quickly (if you have access to the internet).</p><h3 id=\"cloud-hosted-coding-environments\"><a href=\"#cloud-hosted-coding-environments\" class=\"heading-anchor\">Cloud-hosted coding environments</a></h3><p>When you <em>do</em> need to show, or run, code in your session, make sure you can run it in the cloud instead of your own machine! I have all my source code on GitHub, and I’m using <a href=\"https://github.com/features/codespaces\" rel=\"noopener\">GitHub Codespaces</a> so I can run a demo completely in the cloud if necessary. There are many similar solutions out there such as <a href=\"https://www.gitpod.io/\" rel=\"noopener\">Gitpod</a>, <a href=\"https://www.jetbrains.com/space/\" rel=\"noopener\">JetBrains Space</a>, <a href=\"https://codesandbox.io/\" rel=\"noopener\">CodeSandbox</a>.<br>The downside of this solution is that it does require a good internet connection.</p><blockquote><p><strong>TIP</strong>: If your source code is on GitHub, and you just need to show the source code, you can use <a href=\"https://github.com/github/dev\" rel=\"noopener\">github.dev</a>, a lightweight, browser-based editor (based on VSCode) you can access by replacing <code>.com</code> with <code>.dev</code> when typing the repository URL. It even supports some extensions such as the brilliant <a href=\"https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour\" rel=\"noopener\">CodeTour</a>, which is great for showing code in a guided way throughout your session.</p></blockquote><h2 id=\"the-speaker-buddy-system\"><a href=\"#the-speaker-buddy-system\" class=\"heading-anchor\">The Speaker Buddy System</a></h2><p>This is the idea that Mattias and I talked about after my failing laptop experience, and I love it! We named it <strong>Speaker Buddy</strong> (not to be mistaken with <a href=\"https://www.conferencebuddy.io/\" rel=\"noopener\">Conference Buddy</a>), but I’m open to better names. In the solutions mentioned above, you still need access to another laptop (and the internet), and the Speaker Buddy system solves this!</p><h3 id=\"1-for-conference-speakers\"><a href=\"#1-for-conference-speakers\" class=\"heading-anchor\">1. For conference speakers</a></h3><h4 id=\"11-for-speakers-who-want-to-volunteer-as-speaker-buddy\"><a href=\"#11-for-speakers-who-want-to-volunteer-as-speaker-buddy\" class=\"heading-anchor\">1.1. For speakers who want to volunteer as Speaker Buddy</a></h4><p>You can be a Speaker Buddy if you enjoy helping others to be successful during their talks.</p><p>You can help out by:</p><ul class=\"list\"><li>Being kind and respectful (e.g. following the Code of Conduct of the conference).</li><li>Announcing that you’re available to help out as a Speaker Buddy (preferably do this via the conference organizers and their communication channels).</li><li>Agreeing up front how you can help (see 1.2).</li></ul><h4 id=\"12-for-speakers-who-want-a-speaker-buddy\"><a href=\"#12-for-speakers-who-want-a-speaker-buddy\" class=\"heading-anchor\">1.2. For speakers who want a Speaker Buddy</a></h4><p>If you are going to speak at a conference and you want another speaker to have your back in case you run into technical issues or just want a friendly and supportive face on the front row, then this is for you!</p><p>Once the conference agenda is known and the Speaker Buddies are confirmed by the conference organizers, contact one of the Speaker Buddies (who does not speak at the same time as you!) to ask if they are willing to help you out in case of technical issues/laptop failure, or just being there as a friendly face in the audience. If they are, you can discuss the details such as:</p><ul class=\"list\"><li>Supporting you by sitting in the front row (smiling and nodding).</li><li>Have a copy of your presentation on their machine.</li><li>Cloning your code repository, installing SDKs and tools on their machine, if you want to show/run code locally.</li><li>Doing a (remote) dry run via their laptop to be completely prepared for a technical failure. (You also test your database backups right?)</li><li>Is there any other way your buddy can help you out? Can they tell a funny story during the technical issue, ask a relevant question, help out in case you get difficult questions, or do something else to put you at ease?</li></ul><blockquote><p>Even with the existence of cloud-based coding environments, I still suggest having a local environment available in case the internet connection is not good enough.</p></blockquote><h3 id=\"2-for-conference-organizers\"><a href=\"#2-for-conference-organizers\" class=\"heading-anchor\">2. For conference organizers</a></h3><p>I know organizers have a lot to worry about when organizing a conference, and most of the conferences I attended did a good job regarding the AV setup. I prefer when there are hired AV professionals to help out, but in case they are not, please ensure there is at least someone from the organization available in the room to assist when technical issues arise.</p><p>Additionally, you can offer your speakers some peace of mind when they know someone has their back in case of technical difficulties (e.g. laptop failure).</p><h3 id=\"21-cfp-preparation\"><a href=\"#21-cfp-preparation\" class=\"heading-anchor\">2.1. CfP preparation</a></h3><p>When preparing the Call for Papers (CfP), add a field to whatever system you’re using to have the speaker indicate if they are willing to be a Speaker Buddy. Include a link to <a href=\"https://github.com/marcduiker/speaker-buddy\" rel=\"noopener\">this repo</a> so they can read more about it.</p><h3 id=\"22-speaker-onboarding/confirmation\"><a href=\"#22-speaker-onboarding/confirmation\" class=\"heading-anchor\">2.2. Speaker onboarding/confirmation</a></h3><p>When onboarding/confirming speakers to your conference, mention the Speaker Buddy System and provide the list of speakers who have volunteered to be a Speaker Buddy.</p><p>Feel free to use this template in your communication:</p><hr><p>Hi {speaker},</p><p>We highly encourage you to look for a Speaker Buddy before attending our conference.</p><p>A Speaker Buddy is another speaker that can help you out in case of technical issues (e.g. laptop failure).</p><p>These speakers are volunteering as Speaker Buddies:</p><ul class=\"list\"><li>{list of speakers}</li></ul><p>Our conference agenda is published at {conference agenda url}, so look for a Speaker Buddy that is available during your session.</p><p>It’s best to prepare well in advance. For more information on how to be prepared, read the tips at <a href=\"https://github.com/marcduiker/speaker-buddy\" rel=\"noopener\">https://github.com/marcduiker/speaker-buddy</a>.</p><hr><h3 id=\"3-preparing-the-event-location\"><a href=\"#3-preparing-the-event-location\" class=\"heading-anchor\">3. Preparing the event location</a></h3><p>When you prepare the conference rooms, label a front-row seat for the Speaker Buddy. This way, they can be supportive and help out the speaker quickly in case of technical issues.</p><h2 id=\"lets-use-it\"><a href=\"#lets-use-it\" class=\"heading-anchor\">Let’s use it!</a></h2><p>I’ve put together a <a href=\"https://github.com/marcduiker/speaker-buddy\" rel=\"noopener\">repository</a> that contains most of the info in this blog post. I highly encourage conference organizers to refer to this repo in their communication to inform speakers about the Speaker Buddy System.</p><p>If you see an opportunity for improvement, please submit an issue or pull request! ❤️</p>",
      "date_published": "2022-11-24T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/one-year-at-ably-as-developer-advocate/",
      "url": "https://marcduiker.dev/articles/one-year-at-ably-as-developer-advocate/",
      "title": "One year at Ably as a Developer Advocate",
      "content_html": "<p>It has been one year since I’ve joined the DevRel team at Ably as Sr Developer Advocate. In this post I’ll highlight some things I’ve been working on and what I’ve learned in the past year. I’ll cover content creation, developer tooling, events, roadmap &amp; predictability, communication &amp; relationships, and finally ‘anything that could be improved’.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/63.1.DevRel_team_marc_selected-440w.webp 440w, /assets/images/63.1.DevRel_team_marc_selected-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/63.1.DevRel_team_marc_selected-650w.webp\" width=\"650\" height=\"365\" alt=\"Ably DevRel team\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"content-creation\"><a href=\"#content-creation\" class=\"heading-anchor\">Content creation</a></h2><p>Before I joined Ably, I mostly worked on event-driven architectures using .NET-based backends and Azure services (Azure Functions, CosmosDB, Service Bus). Ably really shines when real-time communication is needed between clients, or between servers and clients, so this meant I had to get up to speed with client-side programming, and WebSocket-based communication using the Ably SDK.</p><p>During my onboarding, I created my first demo, <a href=\"https://agileflush.ably.dev/\" rel=\"noopener\">Agile Flush</a>, a small and playful web app to do remote planning poker. This was my first introduction to VueJS and the Ably client SDK, and after a while I got to like working with Vue. It did take me some time to get used to the toolchain though.</p><p>I’ve stuck to using Vue (TypeScript based, with Vite for the dev tooling) for most of my demos where I need a front-end. It pairs nicely with Azure Static Web Apps that I use for hosting the demos.</p><p>Here’s a list with all the demos I’ve created so far, including related content pieces.</p><h3 id=\"agile-flush\"><a href=\"#agile-flush\" class=\"heading-anchor\">Agile Flush</a></h3><p>A playful web app for remote poker planning. The clients publish their votes and actions to Ably, and Ably pushes the updates to the subscribed clients.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/63.2.agileflush_screenshot-440w.webp 440w, /assets/images/63.2.agileflush_screenshot-650w.webp 650w, /assets/images/63.2.agileflush_screenshot-960w.webp 960w, /assets/images/63.2.agileflush_screenshot-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/63.2.agileflush_screenshot-1200w.webp\" width=\"1200\" height=\"703\" alt=\"Agile Flush web app\" loading=\"lazy\" decoding=\"async\"></picture></p><ul class=\"list\"><li><a href=\"https://ably.com/blog/tutorial-vuejs-nodejs-azure-static-web-apps\" rel=\"noopener\">Blog post</a></li><li><a href=\"https://www.youtube.com/watch?v=59BZCQuRRkM\" rel=\"noopener\">Video</a></li><li><a href=\"https://agileflush.ably.dev/\" rel=\"noopener\">Live demo</a></li><li><a href=\"https://github.com/ably-labs/agile-flush-vue-app\" rel=\"noopener\">GitHub repo</a></li></ul><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/59BZCQuRRkM\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><h3 id=\"what-is-pub/sub-and-how-to-apply-it-in-c-net-to-build-a-chat-app\"><a href=\"#what-is-pub/sub-and-how-to-apply-it-in-c-net-to-build-a-chat-app\" class=\"heading-anchor\">What is pub/sub and how to apply it in C# .NET to build a chat app</a></h3><p>The demo is a .NET 6 console application that can publish or subscribe to messages which get delivered through Ably. The blog post also covers the basics what pub/sub is, the typical use cases, and benefits.</p><ul class=\"list\"><li><a href=\"https://ably.com/blog/use-pub-sub-to-build-a-chat-app-with-csharp-net\" rel=\"noopener\">Blog post</a></li><li><a href=\"https://github.com/ably-labs/pubsub-demo-dotnet\" rel=\"noopener\">GitHub repo</a></li></ul><h3 id=\"serverless-websockets-quest\"><a href=\"#serverless-websockets-quest\" class=\"heading-anchor\">Serverless WebSockets Quest</a></h3><p>A turn-based mini game with with real-time aspects build on Azure Functions, Durable Functions, and Ably.</p><ul class=\"list\"><li><a href=\"https://ably.com/blog/quest-for-serverless-websockets-azure-functions-adventure\" rel=\"noopener\">Blog post</a></li><li><a href=\"https://www.youtube.com/watch?v=KHzdc3USFU4\" rel=\"noopener\">Video</a></li><li><a href=\"https://quest.ably.dev/\" rel=\"noopener\">Live demo</a></li><li><a href=\"https://github.com/ably-labs/serverless-websockets-quest\" rel=\"noopener\">GitHub repo</a></li></ul><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/KHzdc3USFU4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><h3 id=\"collaborative-pixelart-drawing\"><a href=\"#collaborative-pixelart-drawing\" class=\"heading-anchor\">Collaborative Pixelart Drawing</a></h3><p>A collaborative pixelart drawing canvas. Multiple users draw on a canvas, and their movement and actions are shared with the other connected users.</p><p>I created two versions of this demo, one using Ably, and one using Azure Web PubSub, and did a write-up about difference in developer experience between the two services.</p><ul class=\"list\"><li><a href=\"https://ably.com/blog/cloud-pubsub-services-compared-azure-web-pubsub-ably\" rel=\"noopener\">Blog post</a></li><li><a href=\"https://www.youtube.com/watch?v=sPgHwm3-yiM\" rel=\"noopener\">Video</a></li><li><a href=\"https://pixel-paint.ably.dev/\" rel=\"noopener\">Live demo</a></li><li><a href=\"https://github.com/ably-labs/collaborative-pixel-drawing\" rel=\"noopener\">GitHub repo</a></li></ul><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/sPgHwm3-yiM\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><h3 id=\"serverless-pizza-workflow-visualizer\"><a href=\"#serverless-pizza-workflow-visualizer\" class=\"heading-anchor\">Serverless Pizza Workflow Visualizer</a></h3><p>A demo that visualizes the real-time progress of a serverless workflow that is built with Durable Functions. I really had a lot of fun creating the pizza-themed visuals for this demo.</p><ul class=\"list\"><li><a href=\"https://ably.com/blog/visualize-azure-serverless-workflow-progress-in-realtime-with-pubsub\" rel=\"noopener\">Blog post</a></li><li><a href=\"https://www.youtube.com/watch?v=y9-a_ewgWCQ\" rel=\"noopener\">Video</a></li><li><a href=\"https://pizza.ably.dev/\" rel=\"noopener\">Live demo</a></li><li><a href=\"https://github.com/ably-labs/serverless-workflow-visualizer\" rel=\"noopener\">GitHub repo</a></li></ul><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/y9-a_ewgWCQ\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><h2 id=\"developer-tooling\"><a href=\"#developer-tooling\" class=\"heading-anchor\">Developer tooling</a></h2><p>I <strong>really</strong> like improving the developer experience from a tooling perspective. Ably has a Control API, a RESTful interface that allows management of the Ably apps. I’ve used this API in several tools to speed up the creation of apps and keys.</p><h3 id=\"ably-control-api-github-action\"><a href=\"#ably-control-api-github-action\" class=\"heading-anchor\">Ably Control API GitHub Action</a></h3><p>Since most of my demos require a new Ably App, I created a GitHub action that creates an app from a GitHub workflow. The action also enables the the creation of an API key with a configurable set of capabilities. This was the first time I’ve created a GitHub action, which was a great learning experience for me. The action has a limited feature set, but I intend to add more features in the future.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/63.7.ably-control-api-action-440w.webp 440w, /assets/images/63.7.ably-control-api-action-650w.webp 650w, /assets/images/63.7.ably-control-api-action-960w.webp 960w, /assets/images/63.7.ably-control-api-action-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/63.7.ably-control-api-action-1200w.webp\" width=\"1200\" height=\"367\" alt=\"Ably Control API GitHub Action\" loading=\"lazy\" decoding=\"async\"></picture></p><ul class=\"list\"><li><a href=\"https://ably.com/blog/infrastructure-as-code-ably-control-api-github-action\" rel=\"noopener\">Blog post</a></li><li><a href=\"https://www.youtube.com/watch?v=b7GE39JaM3M\" rel=\"noopener\">Video</a></li><li><a href=\"https://github.com/marketplace/actions/ably-control-api\" rel=\"noopener\">Marketplace</a></li><li><a href=\"https://github.com/ably-labs/ably-control-api-action\" rel=\"noopener\">GitHub repo</a></li></ul><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/b7GE39JaM3M\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><h3 id=\"ably-vscode-extension\"><a href=\"#ably-vscode-extension\" class=\"heading-anchor\">Ably VSCode extension</a></h3><p>During the first Ably Innovation Days, I proposed to create a VSCode extension that allows developers to manage their Ably apps directly from VSCode. I formed a team with several colleagues, and in two days we had a working prototype that lists all the Ably apps in the activity bar, and creates a new Ably app via the command palette. We won the <em>Ship It 🚢</em> award with this prototype, which allowed me to continue working on the extension and release it to the VSCode extension marketplace.</p><ul class=\"list\"><li><a href=\"https://ably.com/blog/announcing-the-ably-vs-code-extension\" rel=\"noopener\">Blog post</a></li><li><a href=\"https://www.youtube.com/watch?v=3CPHb_kn1-o\" rel=\"noopener\">Video</a></li><li><a href=\"https://marketplace.visualstudio.com/items?itemName=ably-labs.vscode-ably\" rel=\"noopener\">Marketplace</a></li><li><a href=\"https://github.com/ably-labs/vscode-ably\" rel=\"noopener\">GitHub repo</a></li></ul><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/3CPHb_kn1-o\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><h3 id=\"ably-cli\"><a href=\"#ably-cli\" class=\"heading-anchor\">Ably CLI</a></h3><p>During the second Ably Innovation Days, I started working on specifications for an Ably CLI. After the first day <a href=\"https://twitter.com/leggetter\" rel=\"noopener\">Phil</a> and I started with a prototype based on <a href=\"https://oclif.io/\" rel=\"noopener\">oclif</a>. We managed to create a working prototype in a day that lists Ably apps, and creates a new Ably app. This project is still Work In Progress. Once the CLI is in a releasable state, I’ll create some content around this.</p><ul class=\"list\"><li><a href=\"https://github.com/ably-labs/ably-cli\" rel=\"noopener\">GitHub repo</a></li></ul><h2 id=\"events\"><a href=\"#events\" class=\"heading-anchor\">Events</a></h2><p>I had the opportunity to speak at several conferences and meetups this year, both online and in-person. It was great to attend in-person events again, networking with other people, seeing people respond to your talk, and people asking questions afterwards. I’m still a proponent of online (or hybrid) events as well though. Online events can be more accessible, and allow for a more diverse audience.</p><p>Events I spoke at:</p><ul class=\"list\"><li><a href=\"https://azurelowlands.com/\" rel=\"noopener\">Azure Lowlands</a></li><li><a href=\"https://ndcoslo.com/\" rel=\"noopener\">NDC Oslo</a></li><li><a href=\"https://dotnetdays.ro/\" rel=\"noopener\">dotnetdays Romania</a></li><li><a href=\"https://www.youtube.com/watch?v=LpzJTJvH6go&amp;t=3453s\" rel=\"noopener\">CosmosDB Conference</a></li><li><a href=\"https://youtu.be/Y0YTtgn5KKo\" rel=\"noopener\">ServerlessDays NYC</a></li><li><a href=\"https://www.youtube.com/watch?v=FGklbFQrd44\" rel=\"noopener\">Microsoft Reactor Toronto</a></li><li><a href=\"https://www.dotnetoxford.com/posts/2022-02-lightning-talks\" rel=\"noopener\">Oxford .NET User Group</a></li><li><a href=\"https://www.youtube.com/watch?v=R87-35-Aiw8\" rel=\"noopener\">Welsh Azure User Group</a></li><li><a href=\"https://www.sharepointeurope.com/webinars/start-building-serverless-applications-on-azure/\" rel=\"noopener\">ESPC Community Webinar</a></li></ul><h2 id=\"roadmap-and-predictability\"><a href=\"#roadmap-and-predictability\" class=\"heading-anchor\">Roadmap &amp; Predictability</a></h2><p>Part of my role involves creating and updating a roadmap for the .NET &amp; Azure community. This document refers to the company strategy, and how the DevRel team activities fit in that strategy. The roadmap contains the topics which I plan to create content for, estimations on their outcomes, and a timeline. The metrics &amp; predictability of the outcomes is really one of the most difficult parts in DevRel. One blog post can go viral while the next one hardly gets any engagement. It’s important to try understand why something works well or why it doesn’t.</p><p>What I’ve learned over the last year is that content creation is just a small part of the bigger picture when working in DevRel. Content distribution is just as important. If I write a great blog post but hardly anyone will read it, then writing it has been a waste of my time. I’m spending more time these days on the distribution part, to make sure the content gets the proper exposure across various channels. It’s not a part I particularly like, but it needs to be done, and I do appreciate the new insights it gives me.</p><p>Reflecting on the content, the engagement, and analyzing the performance (visits, session duration, sign-ups etc) is even more important. If a content piece is not resulting in (enough) sign-ups, then we investigate why, change the content, or use a different approach for future pieces. We’re measuring several aspects for a couple of months now and it still feels like we’re at the beginning of understanding the numbers. It’s certainly an area that interests me, and where I want to improve the predictability of my work. Being in DevRel means that you’re constantly learning, measuring, and tweaking. And that’s what I love about it.</p><h2 id=\"communication-and-relationships\"><a href=\"#communication-and-relationships\" class=\"heading-anchor\">Communication &amp; Relationships</a></h2><p>So far, it looks like this is all a one-person-show, but it’s far from that.</p><p>I depend on:</p><ul class=\"list\"><li>My DevRel and content team colleagues, who help me review the content I create.</li><li>Our community manager, who informs me of CfPs, sponsoring opportunities, and new questions on Discord and social media.</li><li>Our content marketing team, to provide me with insights on keyword research and content performance.</li><li>Our product team, to keep me up to date with the latest product features and changes.</li><li>My managers, to provide me with valuable feedback about my work, so I can keep improving.</li></ul><p>The <em>relations</em> part of DevRel is really crucial. In order to have a good relation with your developer audience, you also need to have a good relation with your team members, Marketing, Product, Documentation, Engineering, and Customer Support. Establishing and growing these relations take time. New people join the company and others leave, it’s an ever changing environment. It can be tiring and challenging at times, but once the relationship is there, it feels good to work together to achieve a common goal.</p><h2 id=\"anything-that-could-be-improved\"><a href=\"#anything-that-could-be-improved\" class=\"heading-anchor\">Anything that could be improved?</a></h2><p>Always! 😅 I noticed that I spent less time on my open source projects than before I joined Ably. It’s probably because I now create content for a living, I don’t always feel like doing that in my spare time as well. I still have plenty of plans to continue working on these projects though, it just needs a bit more planning and prioritization.</p><p>One of the areas I really want to expand on is collaborations with other people. These could be collaborations with other DevRel folks, or individual developers who share a common interest around serverless, real-time communication, pixelart, or something completely out of the box! If you think we should combine forces to work on a fun project together, please <a href=\"https://twitter.com/marcduiker\" rel=\"noopener\">reach out</a>!</p>",
      "date_published": "2022-11-02T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-deployment-lesson-dotnet6/",
      "url": "https://marcduiker.dev/articles/azfuncuni-deployment-lesson-dotnet6/",
      "title": "Azure Functions University - Deployment Lesson (.NET 6)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/62.AzureFunctionsUniversity_Deployment_Lesson_NET6-440w.webp 440w, /assets/images/62.AzureFunctionsUniversity_Deployment_Lesson_NET6-650w.webp 650w, /assets/images/62.AzureFunctionsUniversity_Deployment_Lesson_NET6-960w.webp 960w, /assets/images/62.AzureFunctionsUniversity_Deployment_Lesson_NET6-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/62.AzureFunctionsUniversity_Deployment_Lesson_NET6-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Deployment Lesson .NET 6\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to deploy Azure Functions written in .NET 6.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/dotnet6/deployment/README.md\" rel=\"noopener\">Deployment to Azure (.NET 6)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/RgF8bA1-CMo\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Understanding the Azure Resources</td></tr><tr><td>2</td><td>Deployment using VSCode</td></tr><tr><td>3</td><td>Creating Azure Resources using Azure CLI</td></tr><tr><td>4</td><td>Deployment using Azure Functions CLI</td></tr><tr><td>5</td><td>Deployment using GitHub Actions</td></tr><tr><td>6</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2022-03-15T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-durablefunctions-advanced-lesson-typescript/",
      "url": "https://marcduiker.dev/articles/azfuncuni-durablefunctions-advanced-lesson-typescript/",
      "title": "Azure Functions University - Durable Functions Advanced Patterns (TypeScript)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/61.AzureFunctionsUniversity_DurableFunctions_Lesson2_typescript-440w.webp 440w, /assets/images/61.AzureFunctionsUniversity_DurableFunctions_Lesson2_typescript-650w.webp 650w, /assets/images/61.AzureFunctionsUniversity_DurableFunctions_Lesson2_typescript-960w.webp 960w, /assets/images/61.AzureFunctionsUniversity_DurableFunctions_Lesson2_typescript-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/61.AzureFunctionsUniversity_DurableFunctions_Lesson2_typescript-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Durable Functions Lesson 2 TypeScript\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use some advanced Durable Functions patterns in Azure Functions written in TypeScript.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/tree/main/lessons/typescript/durable-functions/advanced/README.md\" rel=\"noopener\">Durable Functions - Advanced Patterns (TypeScript)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/iYij7Mf7_dE\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Scenario</td></tr><tr><td>2</td><td>Fan-Out/Fan-In</td></tr><tr><td>3</td><td>Sub-Orchestration</td></tr><tr><td>4</td><td>External Events - Human Interaction</td></tr><tr><td>5</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2022-02-25T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-callingrestapis-lesson-dotnet6/",
      "url": "https://marcduiker.dev/articles/azfuncuni-callingrestapis-lesson-dotnet6/",
      "title": "Azure Functions University - Calling third-party REST APIs (.NET 6)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/60.AzureFunctionsUniversity_Calling3rdpartyRESTAPIs_dotnet6-440w.webp 440w, /assets/images/60.AzureFunctionsUniversity_Calling3rdpartyRESTAPIs_dotnet6-650w.webp 650w, /assets/images/60.AzureFunctionsUniversity_Calling3rdpartyRESTAPIs_dotnet6-960w.webp 960w, /assets/images/60.AzureFunctionsUniversity_Calling3rdpartyRESTAPIs_dotnet6-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/60.AzureFunctionsUniversity_Calling3rdpartyRESTAPIs_dotnet6-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Calling third-party REST APIs .NET 6\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to call 3rd party REST APIs in Azure Functions written in .NET 6.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/dotnet6/http-refit/README.md\" rel=\"noopener\">Calling third-party REST APIs (.NET 6)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/11Qi8A_8cVY\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Creating a default HTTP-triggered function app</td></tr><tr><td>2</td><td>Defining a third-party REST API</td></tr><tr><td>3</td><td>Adding custom API parameters</td></tr><tr><td>4</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2022-02-13T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-http-lesson-dotnet6/",
      "url": "https://marcduiker.dev/articles/azfuncuni-http-lesson-dotnet6/",
      "title": "Azure Functions University - HTTP Lesson (.NET 6)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/59.AzureFunctionsUniversity_HTTP_Lesson_dotnet6-440w.webp 440w, /assets/images/59.AzureFunctionsUniversity_HTTP_Lesson_dotnet6-650w.webp 650w, /assets/images/59.AzureFunctionsUniversity_HTTP_Lesson_dotnet6-960w.webp 960w, /assets/images/59.AzureFunctionsUniversity_HTTP_Lesson_dotnet6-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/59.AzureFunctionsUniversity_HTTP_Lesson_dotnet6-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Http Lesson .NET 6\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the HTTP trigger in Azure Functions written in .NET 6.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/dotnet6/http/README.md\" rel=\"noopener\">HTTP Trigger (.NET 6)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/aifFp86G3tI\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Creating a Function App</td></tr><tr><td>2</td><td>Changing the template for GET requests</td></tr><tr><td>3</td><td>Adding a BadRequest response</td></tr><tr><td>4</td><td>Handling POST requests with string data</td></tr><tr><td>5</td><td>Handling POST requests with JSON data</td></tr><tr><td>6</td><td>Changing the route for a custom greeting</td></tr><tr><td>7</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2022-01-03T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-http-lesson-python/",
      "url": "https://marcduiker.dev/articles/azfuncuni-http-lesson-python/",
      "title": "Azure Functions University - HTTP Lesson (Python)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/58.AzureFunctionsUniversity_HTTP_Lesson_python-440w.webp 440w, /assets/images/58.AzureFunctionsUniversity_HTTP_Lesson_python-650w.webp 650w, /assets/images/58.AzureFunctionsUniversity_HTTP_Lesson_python-960w.webp 960w, /assets/images/58.AzureFunctionsUniversity_HTTP_Lesson_python-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/58.AzureFunctionsUniversity_HTTP_Lesson_python-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Http Lesson Python\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the HTTP trigger in Azure Functions written in Python.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/python/http/README.md\" rel=\"noopener\">HTTP Trigger (Python)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/fDnPGeRTwHc\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Creating a Function App</td></tr><tr><td>2</td><td>Changing the template for GET requests</td></tr><tr><td>3</td><td>Changing the template for POST requests</td></tr><tr><td>4</td><td>Adding a new function for POST requests</td></tr><tr><td>5</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2021-12-10T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-http-lesson-powershell-portal/",
      "url": "https://marcduiker.dev/articles/azfuncuni-http-lesson-powershell-portal/",
      "title": "Azure Functions University - HTTP Lesson (PowerShell via Portal)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/57.AzureFunctionsUniversity_HTTP_Lesson_PowerShell-440w.webp 440w, /assets/images/57.AzureFunctionsUniversity_HTTP_Lesson_PowerShell-650w.webp 650w, /assets/images/57.AzureFunctionsUniversity_HTTP_Lesson_PowerShell-960w.webp 960w, /assets/images/57.AzureFunctionsUniversity_HTTP_Lesson_PowerShell-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/57.AzureFunctionsUniversity_HTTP_Lesson_PowerShell-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Http Lesson PowerShell\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the HTTP trigger in PowerShell Azure Functions in the Azure Portal.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/PowerShell/http/http-lesson-powershell-portal.md\" rel=\"noopener\">HTTP Trigger (PowerShell via the Azure Portal)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/w0FcA7Prnjk\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Creating a Function App</td></tr><tr><td>2</td><td>Take a tour of the interface</td></tr><tr><td>3</td><td>Creating your first Function</td></tr><tr><td>4</td><td>Changing the template for GET requests</td></tr><tr><td>5</td><td>Changing the template for POST requests</td></tr><tr><td>6</td><td>Changing the name of the Request parameter</td></tr><tr><td>7</td><td>Change the route for a custom greeting</td></tr><tr><td>8</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2021-10-24T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-blob-lesson-typescript/",
      "url": "https://marcduiker.dev/articles/azfuncuni-blob-lesson-typescript/",
      "title": "Azure Functions University - Blob Lesson (TypeScript)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/56.AzureFunctionsUniversity_Blob_Lesson_typescript-440w.webp 440w, /assets/images/56.AzureFunctionsUniversity_Blob_Lesson_typescript-650w.webp 650w, /assets/images/56.AzureFunctionsUniversity_Blob_Lesson_typescript-960w.webp 960w, /assets/images/56.AzureFunctionsUniversity_Blob_Lesson_typescript-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/56.AzureFunctionsUniversity_Blob_Lesson_typescript-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Blob Lesson TypeScript\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the Blob trigger &amp; bindings in Azure Functions written in TypeScript.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/typescript/blob/README.md\" rel=\"noopener\">Blob Trigger &amp; Bindings (TypeScript)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/SC4-_ZwjlR4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Using the Microsoft Azure Storage Explorer and Azurite</td></tr><tr><td>2</td><td>Using plain Blob output bindings</td></tr><tr><td>3</td><td>Using binding expressions for Blob output bindings</td></tr><tr><td>4</td><td>Using payload data of trigger for Blob output bindings</td></tr><tr><td>5</td><td>Using plain Blob input bindings</td></tr><tr><td>6</td><td>Using Blob storage SDK for reading</td></tr><tr><td>7</td><td>Creating a Blob triggered Function</td></tr><tr><td>8</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2021-09-03T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-lesson-tips/",
      "url": "https://marcduiker.dev/articles/azfuncuni-lesson-tips/",
      "title": "Azure Functions University - Lesson Tips",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/55.AzureFunctionsUniversity_Lesson_Tips-440w.webp 440w, /assets/images/55.AzureFunctionsUniversity_Lesson_Tips-650w.webp 650w, /assets/images/55.AzureFunctionsUniversity_Lesson_Tips-960w.webp 960w, /assets/images/55.AzureFunctionsUniversity_Lesson_Tips-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/55.AzureFunctionsUniversity_Lesson_Tips-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Lesson Tip\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, since the amount of Azure Functions University content is growing rapidly, I’ve made a video on how you can get started with Azure Functions University.</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/xJEi8Mofp0A\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>This video contains these topics:<ul class=\"list\"><li>0:00 Intro</li><li>0:30 YouTube</li><li>0:53 YouTube Playlists</li><li>1:34 YouTube Chapters</li><li>2:18 GitHub repo</li><li>3:03 Table of contents links in markdown</li><li>3:44 Cloning the GitHub repo</li><li>5:13 The Azure Functions University folder structure</li><li>6:33 Using VSCode workspaces</li><li>12:22 Using CodeTour</li><li>16:00 Recap and closing</li></ul>",
      "date_published": "2021-05-19T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-http-lesson-powershell/",
      "url": "https://marcduiker.dev/articles/azfuncuni-http-lesson-powershell/",
      "title": "Azure Functions University - HTTP Lesson (PowerShell)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/54.AzureFunctionsUniversity_HTTP_Lesson_PowerShell-440w.webp 440w, /assets/images/54.AzureFunctionsUniversity_HTTP_Lesson_PowerShell-650w.webp 650w, /assets/images/54.AzureFunctionsUniversity_HTTP_Lesson_PowerShell-960w.webp 960w, /assets/images/54.AzureFunctionsUniversity_HTTP_Lesson_PowerShell-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/54.AzureFunctionsUniversity_HTTP_Lesson_PowerShell-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Http Lesson PowerShell\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the HTTP trigger in Azure Functions written in PowerShell.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/PowerShell/http/README.md\" rel=\"noopener\">HTTP Trigger (PowerShell)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/uPzpfAosmZ8\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Creating a Function App</td></tr><tr><td>2</td><td>Changing the template for GET requests</td></tr><tr><td>3</td><td>Changing the template for POST requests</td></tr><tr><td>4</td><td>Changing the name of the Request parameter</td></tr><tr><td>5</td><td>Change the route for a custom greeting</td></tr><tr><td>6</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2021-05-08T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azure-functions-university/",
      "url": "https://marcduiker.dev/articles/azure-functions-university/",
      "title": "Creating Azure Functions University, an open source educational project",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/53.1.AzureFunctionsUniversity_YouTube_Lesson1-440w.webp 440w, /assets/images/53.1.AzureFunctionsUniversity_YouTube_Lesson1-650w.webp 650w, /assets/images/53.1.AzureFunctionsUniversity_YouTube_Lesson1-960w.webp 960w, /assets/images/53.1.AzureFunctionsUniversity_YouTube_Lesson1-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/53.1.AzureFunctionsUniversity_YouTube_Lesson1-1200w.webp\" width=\"1200\" height=\"640\" alt=\"Screenshot of the first Azure Functions University lesson\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"azure-functions-university\"><a href=\"#azure-functions-university\" class=\"heading-anchor\">Azure Functions University</a></h2><p>Azure Functions University is an educational project to learn about Azure Functions, the Functions as a Service offering in Azure. The content is aimed at people who do not have any previous experience with serverless technology and want to learn more about it.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/53.2.AzureFunctionsUniversity_GitHub_Readme-440w.webp 440w, /assets/images/53.2.AzureFunctionsUniversity_GitHub_Readme-650w.webp 650w, /assets/images/53.2.AzureFunctionsUniversity_GitHub_Readme-960w.webp 960w, /assets/images/53.2.AzureFunctionsUniversity_GitHub_Readme-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/53.2.AzureFunctionsUniversity_GitHub_Readme-1200w.webp\" width=\"1200\" height=\"817\" alt=\"Screenshot of the Azure Functions University GitHub repository\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"the-idea-behind-the-project\"><a href=\"#the-idea-behind-the-project\" class=\"heading-anchor\">The idea behind the project</a></h2><p>I started this project because I want to enable newcomers to serverless technology to get up and running with Azure Functions in a very low friction way. Learning new things can be challenging, and frequently, the official documentation alone is not enough to understand a new topic and put it into practice.</p><p>The dual-channel delivery, <a href=\"https://bit.ly/az-func-uni\" rel=\"noopener\">lessons on GitHub</a> and <a href=\"https://bit.ly/az-func-uni-playlist\" rel=\"noopener\">videos on YouTube</a>, is intentional because some people prefer watching (or listening) to videos, while others prefer reading.</p><h2 id=\"how-it-started\"><a href=\"#how-it-started\" class=\"heading-anchor\">How it started</a></h2><p>The Azure Functions University project started in October 2020. I have had quite some content on both GitHub and YouTube for some years now, but most of that was intended for intermediate or experienced users of Azure Functions. Since there is a huge increase in people new to programming, I want to help out that group and make it easy for them to start with serverless technology.</p><p>I consider myself reasonably experienced with Azure Functions. On the one hand, that’s good for the project, so I can share a lot of what I know. But on the other hand, this can be a pitfall because I’m likely to have assumptions on topics that people new to serverless don’t have. To prevent too much bias from my side, I wanted someone relatively new to the technology to co-create the content and co-host the live streams. I was following Gwyneth Pena (US) on Twitter, and since I really like her personality and the style of her videos, I asked her to join. I was thrilled she said yes immediately.</p><p>Gwyneth was changing jobs right after we started, and she couldn’t help out for a while. I had to find others to help create content and co-host the live streams. Luckily some people reached out. There are now contributions from Gabriela Martinez (Mexico), Christian Lechner (Germany), Stacy Cashmore (Netherlands), and Barbara Forbes (Netherlands).</p><h2 id=\"curriculum\"><a href=\"#curriculum\" class=\"heading-anchor\">Curriculum</a></h2><p>At this moment, the curriculum contains the following lessons:</p><ul class=\"list\"><li><strong>HTTP</strong>; How to do GET requests and use query string parameters and do POST requests where the data is read from the request body.</li><li><strong>Blob</strong>; How to use output and input bindings to read/write data from/to Blob storage using different binding types, using the BlobTrigger to start a function when a blob is written to storage.</li><li><strong>Queue</strong>; How to use output bindings with various binding types, using the QueueTrigger to start a function when a message is put in a queue.</li><li><strong>Table</strong>; How to use output and input bindings to read/write data from/to Table storage with various binding types.</li><li><strong>Deployment</strong>; How to deploy your Function App to Azure using VSCode, Azure CLI, and GitHUb Actions.</li><li><strong>Configuration</strong>; Why and how to use app settings in your Function App, using App Configuration service for easier management for app settings across multiple resources.</li><li><strong>CosmosDB</strong>; How to use the output and input bindings to read/write data from/to CosmosDB, using the CosmosDBTrigger to start a function when a new document is added to a collection, and using KeyVault to store the CosmosDB connection string.</li><li><strong>Durable Functions, part I</strong>; Why using Durable Functions is beneficial when dealing with multiple functions. This is demonstrated by using the function chaining pattern to illustrate how orchestrations work.</li></ul><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/53.3.AzureFunctionsUniversity_YouTube_Playlist-440w.webp 440w, /assets/images/53.3.AzureFunctionsUniversity_YouTube_Playlist-650w.webp 650w, /assets/images/53.3.AzureFunctionsUniversity_YouTube_Playlist-960w.webp 960w, /assets/images/53.3.AzureFunctionsUniversity_YouTube_Playlist-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/53.3.AzureFunctionsUniversity_YouTube_Playlist-1200w.webp\" width=\"1200\" height=\"774\" alt=\"Screenshot of the Azure Functions University playlist on YouTube\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I believe that consistency is key when creating educational content. Therefore each lesson follows the same structure:</p><ul class=\"list\"><li>there are several exercises written in markdown, including code snippets;</li><li>three types of call-outs are used: 📝 tips , 🔍 observations and ❓ questions;</li><li>a complete Function App project is available as reference;</li><li>at the end of each lesson, there’s a homework assignment.</li></ul><p>All coding exercises use VSCode as the code editor because I find this is a more beginner-friendly environment than Visual Studio 2019.</p><p>Although we started creating content for .NET functions, we’re now also accepting contributions for other languages. We have some lessons in TypeScript and PowerShell as well now.</p><h2 id=\"challenges\"><a href=\"#challenges\" class=\"heading-anchor\">Challenges</a></h2><p>Creating quality content is hard, and it is very time-consuming. For the first couple of lessons, I created most of the content myself, which was hard to combine with a full-time job. Since more people are helping now, it gets easier, although reviewing the pull requests is a considerable effort. I want to ensure the tone of the lessons remains constant and that inclusive language is used. I now realize what it feels like to be a maintainer of a small open source project.</p><p>The frequency between the lessons varies between two to four weeks. Ideally, I’d like to have a livestream every other week. However, planning is tricky since schedules and priorities shift, not only mine but also the contributors. This is voluntary work we all do in our free time, and sometimes other things are more important, and that’s OK. Working on this project should be enjoyable, not stressful.</p><p>Keeping the lessons up-to-date is becoming a challenge right now. The current .NET content is targeted for .NET Core 3.1. Since Functions can now also be written in .NET 5, additional content needs to be created soon to reflect this. The .NET Core content will remain since .NET Core 3.1 has long-term support, and I expect the content will remain relevant for a while.</p><p>This brings us to another challenge, and that is the Azure Functions University GitHub repository. At the moment, there are eight lessons across three programming languages, .NET Core, TypeScript and PowerShell. Sub-folders are used for each language in order to keep everything tidy. VSCode workspaces are used for the specific lessons. Eventually, the source code needs to be split into separate repositories for each language/runtime. This will make the source code easier to manage, and VSCode will be less confused about which projects to run when the entire folder is opened.</p><h2 id=\"whats-next\"><a href=\"#whats-next\" class=\"heading-anchor\">What’s next?</a></h2><p>There’s a lot of progress to be made. First, there is still a lot of new content to be written. Many topics have not been touched yet, e.g., security, monitoring, SignalR, EventGrid. There are also content translations to the other languages that Azure Functions supports. Some people did show interest in helping out with Python and TypeScript, but it’s still a long way to go until that’s on the same level as the .NET lessons.</p><p>Secondly, I want to have better insight into how many people are using the GitHub repo and how they experience it. I’ll be looking into GitHub classroom to see if I can get a better grip on the usage of the lessons. I prefer to have as little friction as possible, because additional sign-up boundaries might prevent people from using the material.</p><p>Will this project ever be finished? Not any time soon, I think. The Azure Functions team recently presented their roadmap for the next major releases. I expect plenty of opportunities to create new lessons and help more people to use serverless technology.</p><h2 id=\"help-us\"><a href=\"#help-us\" class=\"heading-anchor\">Help us!</a></h2><p>We’re always looking for contributors who can help create content and co-host a live stream! Contributions can be new lessons, additions to existing lessons, or ‘translations’ to other programming languages (TypeScript, Python, PowerShell, Java).</p><p>Please have a look at the existing issues to see if you can contribute to those. If there is nothing to your liking, you can submit a new issue. You don’t need to be an expert on the topic. We can work on the content together. There’s quite an extensive <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/CONTRIBUTING.md\" rel=\"noopener\">contribution guide</a>.</p><h2 id=\"links\"><a href=\"#links\" class=\"heading-anchor\">Links</a></h2><ul class=\"list\"><li>YouTube playlist: <a href=\"https://bit.ly/az-func-uni-playlist\" rel=\"noopener\">https://bit.ly/az-func-uni-playlist</a></li><li>Azure Functions University GitHub repo: <a href=\"http://bit.ly/az-func-uni\" rel=\"noopener\">http://bit.ly/az-func-uni</a></li><li>GitHub issue list: <a href=\"http://bit.ly/az-func-uni-issues\" rel=\"noopener\">http://bit.ly/az-func-uni-issues</a></li></ul>",
      "date_published": "2021-04-27T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-http-lesson-typescript/",
      "url": "https://marcduiker.dev/articles/azfuncuni-http-lesson-typescript/",
      "title": "Azure Functions University - HTTP Lesson (TypeScript)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/52.AzureFunctionsUniversity_HTTP_Lesson_typescript-440w.webp 440w, /assets/images/52.AzureFunctionsUniversity_HTTP_Lesson_typescript-650w.webp 650w, /assets/images/52.AzureFunctionsUniversity_HTTP_Lesson_typescript-960w.webp 960w, /assets/images/52.AzureFunctionsUniversity_HTTP_Lesson_typescript-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/52.AzureFunctionsUniversity_HTTP_Lesson_typescript-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Http Lesson TypeScript\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the HTTP trigger in Azure Functions written in TypeScript.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/typescript/http/README.md\" rel=\"noopener\">HTTP Trigger (TypeScript)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/zYb5sVQgUN4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Creating a Function App</td></tr><tr><td>2</td><td>Changing the template for GET requests</td></tr><tr><td>3</td><td>Changing the template for POST requests</td></tr><tr><td>4</td><td>Adding a new function for POST requests</td></tr><tr><td>5</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2021-04-15T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-configuration-lesson-dotnet/",
      "url": "https://marcduiker.dev/articles/azfuncuni-configuration-lesson-dotnet/",
      "title": "Azure Functions University - Configuration Lesson (.NET Core)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/51.AzureFunctionsUniversity_Configuration_Lesson_dotnetcore-440w.webp 440w, /assets/images/51.AzureFunctionsUniversity_Configuration_Lesson_dotnetcore-650w.webp 650w, /assets/images/51.AzureFunctionsUniversity_Configuration_Lesson_dotnetcore-960w.webp 960w, /assets/images/51.AzureFunctionsUniversity_Configuration_Lesson_dotnetcore-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/51.AzureFunctionsUniversity_Configuration_Lesson_dotnetcore-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Configuration Lesson .NET Core\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use Configuration &amp; Settings in Azure Functions written in .NET Core 3.1.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/dotnetcore31/configuration/README.md\" rel=\"noopener\">Configuration &amp; Settings (.NET Core)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/p8FVnMSYMpA\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Why do we use configuration?</td></tr><tr><td>2</td><td>Function App application settings</td></tr><tr><td>3</td><td>Using custom application settings</td></tr><tr><td>4</td><td>Manage app settings using Azure CLI</td></tr><tr><td>5</td><td>Using App Configuration Service</td></tr><tr><td>6</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2021-03-28T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-durablefunctions-lesson-typescript/",
      "url": "https://marcduiker.dev/articles/azfuncuni-durablefunctions-lesson-typescript/",
      "title": "Azure Functions University - Durable Functions Introduction &amp; Chaining (TypeScript)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/50.AzureFunctionsUniversity_DurableFunctions_Lesson1_typescript-440w.webp 440w, /assets/images/50.AzureFunctionsUniversity_DurableFunctions_Lesson1_typescript-650w.webp 650w, /assets/images/50.AzureFunctionsUniversity_DurableFunctions_Lesson1_typescript-960w.webp 960w, /assets/images/50.AzureFunctionsUniversity_DurableFunctions_Lesson1_typescript-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/50.AzureFunctionsUniversity_DurableFunctions_Lesson1_typescript-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Durable Functions Lesson 1 TypeScript\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use Durable Functions in Azure Functions written in TypeScript.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/tree/main/lessons/typescript/durable-functions/chaining/README.md\" rel=\"noopener\">Durable Functions - Introduction &amp; Chaining (TypeScript)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/gE130BITP9g\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Introduction to Azure Durable Functions</td></tr><tr><td>2</td><td>Creating a Function App project for a Durable Function</td></tr><tr><td>3</td><td>Implementing a “Real-World” Scenario</td></tr><tr><td>4</td><td>Retries - Dealing with Temporal Errors</td></tr><tr><td>5</td><td>Circuit Breaker - Dealing with Timeouts</td></tr><tr><td>6</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2021-02-14T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-cosmosdb-lesson-dotnet/",
      "url": "https://marcduiker.dev/articles/azfuncuni-cosmosdb-lesson-dotnet/",
      "title": "Azure Functions University - CosmosDB Lesson (.NET Core)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/49.AzureFunctionsUniversity_CosmosDB_Lesson_dotnetcore-440w.webp 440w, /assets/images/49.AzureFunctionsUniversity_CosmosDB_Lesson_dotnetcore-650w.webp 650w, /assets/images/49.AzureFunctionsUniversity_CosmosDB_Lesson_dotnetcore-960w.webp 960w, /assets/images/49.AzureFunctionsUniversity_CosmosDB_Lesson_dotnetcore-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/49.AzureFunctionsUniversity_CosmosDB_Lesson_dotnetcore-1200w.webp\" width=\"1200\" height=\"675\" alt=\"CosmosDB Lesson .NET Cor\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the CosmosDB trigger &amp; bindings in Azure Functions written in .NET Core 3.1.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/dotnetcore31/cosmosdb/README.md\" rel=\"noopener\">CosmosDB Trigger &amp; Bindings (.NET Core)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/h_vX3LrQ4l4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Setup the Azure Cosmos DB Emulator</td></tr><tr><td>2</td><td>Using the Cosmos DB output binding</td></tr><tr><td>3</td><td>Using the Cosmos DB input binding</td></tr><tr><td>4</td><td>Creating a Cosmos DB Trigger function</td></tr><tr><td>5</td><td>Deploying to Azure</td></tr><tr><td>6</td><td>Using Azure Key Vault for storing the connection string</td></tr><tr><td>7</td><td>Using Dependency Injection pattern for Cosmos DB connection</td></tr><tr><td>8</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2021-02-07T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-table-lesson-dotnet/",
      "url": "https://marcduiker.dev/articles/azfuncuni-table-lesson-dotnet/",
      "title": "Azure Functions University - Table Lesson (.NET Core)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/48.AzureFunctionsUniversity_Table_Lesson_dotnetcore-440w.webp 440w, /assets/images/48.AzureFunctionsUniversity_Table_Lesson_dotnetcore-650w.webp 650w, /assets/images/48.AzureFunctionsUniversity_Table_Lesson_dotnetcore-960w.webp 960w, /assets/images/48.AzureFunctionsUniversity_Table_Lesson_dotnetcore-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/48.AzureFunctionsUniversity_Table_Lesson_dotnetcore-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Table Lesson .NET Core\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the Table bindings in Azure Functions written in .NET Core 3.1.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/dotnetcore31/table/README.md\" rel=\"noopener\">Table Bindings (.NET Core)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/xiNkCsupUTs\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><br>The lesson consists of the following exercises:<table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Using the Microsoft Azure Storage Explorer for Tables</td></tr><tr><td>2</td><td>Using <code>TableEntity</code> output bindings</td></tr><tr><td>3</td><td>Using <code>IAsyncCollector&lt;T&gt;</code> Table output bindings</td></tr><tr><td>4</td><td>Using <code>TableEntity</code> input bindings</td></tr><tr><td>5</td><td>Using <code>CloudTable</code> input bindings</td></tr><tr><td>6</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2021-01-17T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-deployment-lesson-dotnet/",
      "url": "https://marcduiker.dev/articles/azfuncuni-deployment-lesson-dotnet/",
      "title": "Azure Functions University - Deployment Lesson (.NET Core)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/47.AzureFunctionsUniversity_Deployment_Lesson_dotnetcore-440w.webp 440w, /assets/images/47.AzureFunctionsUniversity_Deployment_Lesson_dotnetcore-650w.webp 650w, /assets/images/47.AzureFunctionsUniversity_Deployment_Lesson_dotnetcore-960w.webp 960w, /assets/images/47.AzureFunctionsUniversity_Deployment_Lesson_dotnetcore-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/47.AzureFunctionsUniversity_Deployment_Lesson_dotnetcore-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Deployment Lesson .NET Core\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to deploy Azure Functions written in .NET Core 3.1.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/dotnetcore31/deployment/README.md\" rel=\"noopener\">Deployment to Azure (.NET Core)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/-B8dE4GTWsk\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><p>The lesson consists of the following exercises:</p><table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Understanding the Azure Resources</td></tr><tr><td>2</td><td>Deployment using VSCode</td></tr><tr><td>3</td><td>Creating Azure Resources using Azure CLI</td></tr><tr><td>4</td><td>Deployment using Azure Functions CLI</td></tr><tr><td>5</td><td>Deployment using GitHub Actions</td></tr><tr><td>6</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2020-12-20T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-queue-lesson-dotnet/",
      "url": "https://marcduiker.dev/articles/azfuncuni-queue-lesson-dotnet/",
      "title": "Azure Functions University - Queue Lesson (.NET Core)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/46.AzureFunctionsUniversity_Queue_Lesson_dotnetcore-440w.webp 440w, /assets/images/46.AzureFunctionsUniversity_Queue_Lesson_dotnetcore-650w.webp 650w, /assets/images/46.AzureFunctionsUniversity_Queue_Lesson_dotnetcore-960w.webp 960w, /assets/images/46.AzureFunctionsUniversity_Queue_Lesson_dotnetcore-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/46.AzureFunctionsUniversity_Queue_Lesson_dotnetcore-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Queue Lesson .NET Core\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the Queue trigger and bindings in Azure Functions written in .NET Core 3.1.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/dotnetcore31/queue/README.md\" rel=\"noopener\">Queue Trigger &amp; Bindings (.NET Core)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/nKJUwW6SGZo\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><p>The lesson consists of the following exercises:</p><table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>1</td><td>Using the Microsoft Azure Storage Explorer for Queues</td></tr><tr><td>2</td><td>Using <code>string</code> Queue output bindings</td></tr><tr><td>3</td><td>Using custom typed Queue output bindings</td></tr><tr><td>4</td><td>Using <code>CloudQueueMessage</code> Queue output bindings</td></tr><tr><td>5</td><td>Using <code>dynamic</code> Queue output bindings</td></tr><tr><td>6</td><td>Using <code>IAsyncCollector&lt;T&gt;</code> Queue output bindings</td></tr><tr><td>7.1</td><td>Creating a default Queue triggered function</td></tr><tr><td>7.2</td><td>Examine &amp; Run the Queue triggered function</td></tr><tr><td>7.3</td><td>Break the Queue triggered function</td></tr><tr><td>7.4</td><td>Change the Queue triggered function</td></tr><tr><td>8</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2020-11-27T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/creative-coding/",
      "url": "https://marcduiker.dev/articles/creative-coding/",
      "title": "How I Got Started With Creative Coding",
      "content_html": "<p>This article is part of <a href=\"https://festivetechcalendar.com/\" rel=\"noopener\">Festive Tech Calendar 2020</a>. An online event organized by the tech community with content created by many kind individuals around the world.</p><hr><p>I really enjoy coding. It think it’s because coding allows me to create something from nothing. That ‘something’ could be a useful feature for a client I work for, but it can also be something for me alone to enjoy. Sometimes the result of what I code doesn’t even do anything useful. It might something that I like looking at or listening to. That sort of coding is also known as creative coding.<br>Here’s the Wikipedia definition:</p><blockquote><p>Creative coding is a type of computer programming in which the goal is to create something expressive instead of functional. It is used to create live visuals for VJing, as well as creating visual art and design, entertainment, art installations, projections and projection mapping, sound art, advertising, product prototypes, and much more.</p></blockquote><p>Source: <a href=\"https://en.wikipedia.org/wiki/Creative_coding\" rel=\"noopener\">Wikipedia</a></p><p>For me, creative coding means creating something enjoyable for myself and, hopefully, also for others. Creative coding can be a myriad of things, allowing you to express yourself in many different ways. I’d like to share my experience with you, and I hope you’ll be inspired to try some creative coding yourself!</p><h2 id=\"games-and-graphics\"><a href=\"#games-and-graphics\" class=\"heading-anchor\">Games &amp; Graphics</a></h2><p>My fascination with creative coding started with computer graphics. This started in my early teenage years while playing <a href=\"https://en.wikipedia.org/wiki/Prince_of_Persia_(1989_video_game)\" rel=\"noopener\">Prince of Persia</a>. The fascination grew over time when I began to create graphics myself. I created 3D landscapes with Bryce, fractal flames using <a href=\"https://en.wikipedia.org/wiki/Apophysis_(software)\" rel=\"noopener\">Apophisis</a>, and other recursive structures using <a href=\"https://en.wikipedia.org/wiki/L-system\" rel=\"noopener\">L-system</a> generators. I’m still intrigued by how well recursive rules can describe nature. Look at these computer-generated fractal weeds 😍:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/45.1.Fractal_weeds-440w.webp 440w, /assets/images/45.1.Fractal_weeds-650w.webp 650w, /assets/images/45.1.Fractal_weeds-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/45.1.Fractal_weeds-960w.webp\" width=\"960\" height=\"562\" alt=\"Fractal Weeds\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"photography\"><a href=\"#photography\" class=\"heading-anchor\">Photography</a></h2><p>For a couple of years, my attention turned to photography and photo editing. I especially enjoyed urban exploration photography, visiting abandoned locations, and capturing the beauty of buildings and interiors in decay. The atmosphere of these locations is breathtaking.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/45.2.salve_mater_urbex-440w.webp 440w, /assets/images/45.2.salve_mater_urbex-650w.webp 650w, /assets/images/45.2.salve_mater_urbex-960w.webp 960w, /assets/images/45.2.salve_mater_urbex-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/45.2.salve_mater_urbex-1200w.webp\" width=\"1200\" height=\"800\" alt=\"Indoor shot of a decayed building\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Decayed interioir of a former sanatorium.</em></p><p>For about two years, I gave photography workshops at the local community college. This experience really helped me to speak in public with more confidence.</p><h2 id=\"visual-arts\"><a href=\"#visual-arts\" class=\"heading-anchor\">Visual arts</a></h2><p>When I learned to code, during my first job, my interest in creating computer graphics spiked again. I discovered <a href=\"https://processing.org/\" rel=\"noopener\">Processing</a>, a software sketchbook. I created many sketches while watching tutorials from Daniel Schiffman (<a href=\"https://www.youtube.com/user/shiffman/\" rel=\"noopener\">The Coding Train</a>). He has a gift for explaining topics on software engineering, maths, and graphics in a very accessible way.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/45.3.the_coding_train-440w.webp 440w, /assets/images/45.3.the_coding_train-650w.webp 650w, /assets/images/45.3.the_coding_train-960w.webp 960w, /assets/images/45.3.the_coding_train-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/45.3.the_coding_train-1200w.webp\" width=\"1200\" height=\"556\" alt=\"The Coding Train on YouTube\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I really recommend <a href=\"https://www.youtube.com/playlist?list=PLRqwX-V7Uu6aFlwukCmDf0-1-uSR7mklK\" rel=\"noopener\">The Nature of Code playlist</a>, which features 83 videos ranging from Perlin noise, my favorite type of noise, to fractals and genetic algorithms.</p><iframe width=\"600\" height=\"600\" src=\"https://editor.p5js.org/marcduiker/embed/LMF39YZcR\"></iframe><p><em><a href=\"https://editor.p5js.org/marcduiker/sketches/LMF39YZcR\" rel=\"noopener\">Noise worms</a>: move your mouse (or tap) over the image to change the noise pattern.</em></p><h2 id=\"retro-game-development\"><a href=\"#retro-game-development\" class=\"heading-anchor\">Retro game development</a></h2><p>I excel in starting new projects and never finishing them 😅. Sometimes I don’t even start them at all 😬. About one and a half years ago, I read about retro game development using <a href=\"https://www.lexaloffle.com/pico-8.php\" rel=\"noopener\">PICO-8</a>. I bought the software with the intent to create some cool games, but I didn’t touch the software for a couple of months. Then, I read about PICO-8 again, this time in a <a href=\"https://magpi.raspberrypi.org/articles/build-retro-game-pico-8-raspberry-pi\" rel=\"noopener\">MagPi magazine</a>. At about the same time, Scott Hanselman invited Joseph White, the creator of PICO-8, on one of his <a href=\"https://hanselminutes.com/703/tiny-games-with-the-pico-8-fantasy-console-and-joseph-white\" rel=\"noopener\">podcasts</a>. This clearly was a sign I had to pick up PICO-8 and create a game with it. Since I am a big fan of Azure Functions, I created a game in which you play the Azure Functions logo and need to collect items to restore the power of an Azure data center.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/45.4.azure_functions_the_game-256w.webp 256w\" sizes=\"90vw\"><img src=\"/assets/images/45.4.azure_functions_the_game-256w.webp\" width=\"256\" height=\"256\" alt=\"Animated gif of Azure Functions, The Game\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em><a href=\"https://marcduiker.itch.io/azure-functions-the-game\" rel=\"noopener\">Azure Functions, The Game</a></em></p><p>Creating this puzzle game took some effort, I spent many evenings and nights over a couple of weeks, but the process was so much fun! PICO-8 is a very restricted environment to create games in, the screen is only 128x128 pixels, sprites are 8x8 pixels, and there’s even a restriction on the amount of code you can write. Once it was finished, I was thrilled the game was well received in the community, the Azure Functions team and others within Microsoft 😊.</p><p>My next game, YuckyYAML, is still in progress. It is a puzzle game, inspired by Sokoban, and is themed around Docker &amp; Kubernetes, so it features whales and containers.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/45.5.yucky_yaml_3-256w.webp 256w\" sizes=\"90vw\"><img src=\"/assets/images/45.5.yucky_yaml_3-256w.webp\" width=\"256\" height=\"256\" alt=\"Animated gif of the Yucky YAML game (work in progress)\" loading=\"lazy\" decoding=\"async\"></picture></p><p>PICO-8 is also an excellent platform to create mini-games. These are very basic games you can play in a minute or so. I created two interactive cards for a lovely 5-year-old, one for his birthday, where he needs to collect all the balloons. And another one for Easter, where he needs to collect eggs. These mini-games are around 100 lines of code and take only an hour or two to create.</p><p>You can play the above mentioned games at <a href=\"https://marcduiker.itch.io/\" rel=\"noopener\">marcduiker.itch.io</a>.</p><p>If you want to learn about game development with PICO-8, I highly recommend <a href=\"https://www.youtube.com/playlist?list=PLea8cjCua_P0qjjiG8G5FBgqwpqMU7rBk\" rel=\"noopener\">this YouTube playlist</a> which contains over 70 videos on how to create a breakout game from start to finish.</p><h2 id=\"ms-build\"><a href=\"#ms-build\" class=\"heading-anchor\">MS Build</a></h2><p>I was very fortunate to be part of MS Build this year. First, I took part in a panel discussion on Serverless APIs, and second, I talked about one of my pet projects, the <a href=\"https://twitter.com/az_func_updates\" rel=\"noopener\">Azure Functions Updates Twitterbot</a>.</p><h3 id=\"8-bit-avatars\"><a href=\"#8-bit-avatars\" class=\"heading-anchor\">8-bit avatars</a></h3><p>After my talks, I wanted to show appreciation to the hosts since I know it’s a lot of effort to keep an event rolling smoothly. So I started creating 8-bit pixel portraits for the hosts. I used a 16x16 pixel format, which is really limited, so it was a challenge drawing everyone in a way they were still recognizable. I posted them on Twitter, and the responses I got were so kind. I kept making the portraits until I had one for each of the hosts. At the end of Build, the hosts gave me a shout-out for my work, which made me very proud. It was a wonderful experience to contribute to Build in this way 😊.</p><h3 id=\"make-code\"><a href=\"#make-code\" class=\"heading-anchor\">Make Code</a></h3><p>One of the last sessions of MS Build was about MakeCode, an educational programming platform backed by various organizations. During <a href=\"https://mybuild.microsoft.com/sessions/a1638103-16a8-4059-90ac-54c7e0dda8a2?source=sessions\" rel=\"noopener\">this session</a>, Louanne Murphy and Scott Hanselman showed how versatile and accessible the platform is. One of the areas where you can use MakeCode is creating <a href=\"https://arcade.makecode.com/\" rel=\"noopener\">arcade games</a>, so obviously, I had to try that. In about 1.5 hours, I completed this mini-game featuring Dona and Seth, competing to eat as many Cheeze-Its within 30 seconds.The game is far from perfect, but the process of making this was a lot of fun!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/45.7.Cheeze-it_Deth_Match-440w.webp 440w, /assets/images/45.7.Cheeze-it_Deth_Match-600w.webp 600w\" sizes=\"90vw\"><img src=\"/assets/images/45.7.Cheeze-it_Deth_Match-600w.webp\" width=\"600\" height=\"702\" alt=\"Screenshot of Cheeze-It Deth Match, a minigame made with MakeCode\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em><a href=\"https://arcade.makecode.com/30800-46845-98441-86480\" rel=\"noopener\">Cheeze-It Deth Match</a> on MakeCode.</em></p><h2 id=\"post-build\"><a href=\"#post-build\" class=\"heading-anchor\">Post Build</a></h2><p>I was really starting to like the 8-bit avatars, so I kept on making some for close (Twitter) friends. Others began to notice the avatars, and the demand kept increasing. Since I’m saving for a Surface Go 2, I decided to ask for a small donation when people ask me to create their avatar. So far, I’ve created 120 of these avatars, and I’m still receiving commissions.<br>Since the avatars I created for Build, I’ve doubled the resolution, I’m now using 32x32 pixels, which is still very limited but allows a bit more detail in hair and clothes. I’m using a limited 32 color palette, which ensures all portraits have the same look &amp; feel.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/45.8.avatars_collage_107frames-440w.webp 440w, /assets/images/45.8.avatars_collage_107frames-640w.webp 640w\" sizes=\"90vw\"><img src=\"/assets/images/45.8.avatars_collage_107frames-640w.webp\" width=\"640\" height=\"640\" alt=\"Animation of various avatars I've made\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"whats-next\"><a href=\"#whats-next\" class=\"heading-anchor\">What’s next</a></h2><p>Creative coding has really become a part of my life. It is ever-changing, sometimes it’s visuals, sometimes music, sometimes games. As long as I can keep creating something that I (and others) like, it gives me a very satisfying feeling, and I will continue doing this for a long time.</p><p>I encourage you to ty creative coding yourself! It doesn’t matter if you are a programming veteran or a novice, you can start very small and build up your skills over time and try different tools and languages. I’m very curious about your creations! Please share them with me on Twitter <a href=\"https://twitter.com/marcduiker\" rel=\"noopener\">@marcduiker</a>.</p><p>If you want to support my creative coding work, please consider a <a href=\"https://ko-fi.com/marcduiker\" rel=\"noopener\">donating a coffee</a> or commission me to create your personalized 8-bit avatar 😊, I draw them entirely by hand.</p>",
      "date_published": "2020-11-23T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-blob-lesson-dotnet/",
      "url": "https://marcduiker.dev/articles/azfuncuni-blob-lesson-dotnet/",
      "title": "Azure Functions University - Blob Lesson (.NET Core)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/44.AzureFunctionsUniversity_Blob_Lesson_dotnetcore-440w.webp 440w, /assets/images/44.AzureFunctionsUniversity_Blob_Lesson_dotnetcore-650w.webp 650w, /assets/images/44.AzureFunctionsUniversity_Blob_Lesson_dotnetcore-960w.webp 960w, /assets/images/44.AzureFunctionsUniversity_Blob_Lesson_dotnetcore-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/44.AzureFunctionsUniversity_Blob_Lesson_dotnetcore-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Blob Lesson .NET Core\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the Blob trigger and bindings in Azure Functions written in .NET Core 3.1.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/dotnetcore31/blob/README.md\" rel=\"noopener\">Blob Trigger &amp; Bindings (.NET Core)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/z5AQdk-43ZI\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><p>The lesson consists of the following exercises:</p><table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>1</td><td>Using the Microsoft Azure Storage Explorer and Storage Emulator</td></tr><tr><td>2</td><td>Using <code>string</code> Blob output bindings</td></tr><tr><td>3</td><td>Using <code>CloudBlobContainer</code> Blob output bindings</td></tr><tr><td>4</td><td>Using <code>dynamic</code> Blob output bindings</td></tr><tr><td>5</td><td>Using <code>Stream</code> Blob input bindings</td></tr><tr><td>6</td><td>Using <code>CloudBlobContainer</code> Blob input bindings</td></tr><tr><td>7</td><td>Using <code>dynamic</code> Blob input bindings</td></tr><tr><td>8</td><td>Creating a Blob triggered function</td></tr><tr><td>9</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2020-11-11T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azfuncuni-http-lesson-dotnet/",
      "url": "https://marcduiker.dev/articles/azfuncuni-http-lesson-dotnet/",
      "title": "Azure Functions University - HTTP Lesson (.NET Core)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/43.AzureFunctionsUniversity_HTTP_Lesson_dotnetcore-440w.webp 440w, /assets/images/43.AzureFunctionsUniversity_HTTP_Lesson_dotnetcore-650w.webp 650w, /assets/images/43.AzureFunctionsUniversity_HTTP_Lesson_dotnetcore-960w.webp 960w, /assets/images/43.AzureFunctionsUniversity_HTTP_Lesson_dotnetcore-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/43.AzureFunctionsUniversity_HTTP_Lesson_dotnetcore-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Http Lesson .NET Core\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Hi serverless friends, we have a new Azure Functions University lesson for you! In this lesson we will learn how to use the HTTP trigger in Azure Functions written in .NET Core 3.1.</p><h2 id=\"lesson\"><a href=\"#lesson\" class=\"heading-anchor\">Lesson</a></h2><p>You can find the lesson on GitHub: <a href=\"https://github.com/marcduiker/azure-functions-university/blob/main/lessons/dotnetcore31/http/README.md\" rel=\"noopener\">HTTP Trigger (.NET Core)</a>.</p><p>And you can watch the video on YouTube:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/5k35dlBAXxA\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe><p>The lesson consists of the following exercises:</p><table><thead><tr><th>Nr</th><th>Exercise</th></tr></thead><tbody><tr><td>0</td><td>Prerequisites</td></tr><tr><td>1</td><td>Creating a Function App</td></tr><tr><td>2</td><td>Changing the template for GET requests</td></tr><tr><td>3</td><td>Changing the template for POST requests</td></tr><tr><td>4</td><td>Adding a new function for POST requests</td></tr><tr><td>5</td><td>Change the route for a custom greeting</td></tr><tr><td>6</td><td>Homework</td></tr></tbody></table>",
      "date_published": "2020-10-28T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/writing-safe-orchestrations/",
      "url": "https://marcduiker.dev/articles/writing-safe-orchestrations/",
      "title": "Durable Functions API - Writing Safe Orchestrations",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/42.safe-orchestrations-cover-440w.webp 440w, /assets/images/42.safe-orchestrations-cover-650w.webp 650w, /assets/images/42.safe-orchestrations-cover-960w.webp 960w, /assets/images/42.safe-orchestrations-cover-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/42.safe-orchestrations-cover-1200w.webp\" width=\"1200\" height=\"674\" alt=\"Slide showing Writing Safe Orchestrations\" loading=\"lazy\" decoding=\"async\"></picture></p><p>This article is part of <a href=\"https://aka.ms/ServerlessSeptember2020\" rel=\"noopener\">#ServerlessSeptember</a>. You’ll find other helpful articles, detailed tutorials, and videos in this all-things-Serverless content collection. New articles from community members and cloud advocates are published every week from Monday to Thursday through September.</p><p>Find out more about how Microsoft Azure enables your Serverless functions at <a href=\"https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=servsept20-devto-cxaall\" rel=\"noopener\">https://docs.microsoft.com/azure/azure-functions/</a>.</p><h2 id=\"writing-safe-orchestrations\"><a href=\"#writing-safe-orchestrations\" class=\"heading-anchor\">Writing Safe Orchestrations</a></h2><p>This post is the fifth part of a series of blogs/vlogs to discover the Durable Functions API.</p><p>In the video linked below, I’m diving into the <a href=\"https://github.com/Azure/azure-functions-durable-extension/releases/tag/Analyzer-v0.3.0\" rel=\"noopener\">Durable Task Analyzer</a>, which is bundled with the Durable Functions extension. This C# Roslyn analyzer helps you to write deterministic code for your orchestrators, so it’s safe to be replayed. The analyzer detects code violations which are described on <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-code-constraints\" rel=\"noopener\">this docs page</a>.</p><h3 id=\"video\"><a href=\"#video\" class=\"heading-anchor\">Video</a></h3><p>Here’s the video, please give it a thumbs up if you like it and please subscribe to my channel if you haven’t done so already:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/ZtIQgR25_Y0\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><h3 id=\"resources\"><a href=\"#resources\" class=\"heading-anchor\">Resources</a></h3><ul class=\"list\"><li>GitHub repo with demo solution containing the code violations &amp; workarounds: <a href=\"https://github.com/marcduiker/demos-durable-task-analyzer\" rel=\"noopener\">github.com/marcduiker/demos-durable-task-analyzer</a>.</li><li>GitHub repo with Durable Functions code snippets: <a href=\"https://github.com/marcduiker/durable-functions-snippets\" rel=\"noopener\">github.com/marcduiker/durable-functions-snippets</a>.</li></ul><h3 id=\"links-to-other-posts-in-this-series\"><a href=\"#links-to-other-posts-in-this-series\" class=\"heading-anchor\">Links to other posts in this series</a></h3><ul class=\"list\"><li><a href=\"/articles/durable-functions-api-durableorchestrationclient-1\">Starting Orchestrations (DurableOrchestrationClient Part 1)</a></li><li><a href=\"/articles/durable-functions-api-durableorchestrationclient-2\">Retrieving the Orchestration Status (DurableOrchestrationClient Part 2)</a></li><li><a href=\"/articles/durable-functions-api-purge-terminate\">Purge &amp; Terminate Orchestrations (DurableOrchestrationClient Part 3)</a></li><li><a href=\"/articles/durable-functions-api-durableorchestrationclient-4\">Human Interaction Pattern (DurableOrchestrationClient Part 4)</a></li></ul>",
      "date_published": "2020-09-16T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/msbuild-serverless-community-creativity/",
      "url": "https://marcduiker.dev/articles/msbuild-serverless-community-creativity/",
      "title": "My MSBuild 2020 experience: serverless, community &amp; creativity",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/41.1.Build_Wallpaper_DarkTheme_DesignA_8bit-440w.webp 440w, /assets/images/41.1.Build_Wallpaper_DarkTheme_DesignA_8bit-650w.webp 650w, /assets/images/41.1.Build_Wallpaper_DarkTheme_DesignA_8bit-960w.webp 960w, /assets/images/41.1.Build_Wallpaper_DarkTheme_DesignA_8bit-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/41.1.Build_Wallpaper_DarkTheme_DesignA_8bit-1200w.webp\" width=\"1200\" height=\"661\" alt=\"8-bit MSBuild wallpaper\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Microsoft just finished its 48-hour live online event, <a href=\"https://mybuild.microsoft.com/\" rel=\"noopener\">MSBuild 2020</a>, and I enjoyed it immensely! I was fortunate to be invited for two sessions: a panel discussion on <em>building serverless APIs</em>, and a <em>connect with the community</em> session. I also threw in some pixel art just for fun, more on that later.</p><h2 id=\"build-and-secure-serverless-apis-panel-discussion\"><a href=\"#build-and-secure-serverless-apis-panel-discussion\" class=\"heading-anchor\">Build and Secure Serverless APIs panel discussion</a></h2><p>My first appearance was during a serverless panel discussion (Session BDL146) together with Rajorshi Ghosh Choudhury (Hasura) and Guy Prodjarny (Snyk). The session was hosted by ☁ 🥑 Simona Cotin. We talked about our definition of serverless, state management, GraphQL, security aspects, and common pitfalls. We covered a lot in just 30 minutes!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/41.2.serverless_panel-440w.webp 440w, /assets/images/41.2.serverless_panel-650w.webp 650w, /assets/images/41.2.serverless_panel-960w.webp 960w, /assets/images/41.2.serverless_panel-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/41.2.serverless_panel-1200w.webp\" width=\"1200\" height=\"672\" alt=\"Build and Secure Serverless APIs panel\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Build and Secure Serverless APIs panel</em></p><p>You can view the <a href=\"https://mybuild.microsoft.com/sessions/e477304a-6de3-4714-a1f3-cc955da82b1a\" rel=\"noopener\">recording here</a>.</p><h2 id=\"community-update\"><a href=\"#community-update\" class=\"heading-anchor\">Community Update</a></h2><p>The second session I was part of, was <em>Connect with Microsoft Western Europe and your local dev community</em> (Session COM178). During this session, many community members took part in panel discussions and gave updates on projects they are working on in their own time.</p><p>I got the opportunity to talk about the <a href=\"https://marcduiker.dev/articles/creating-azure-functions-updates-twitterbot\" rel=\"noopener\">Azure Functions Update Twitter bot</a> that I made with Azure Functions &amp; Durable Functions. I explained the reason why I built it, shared plans for future updates, and mentioned that I’ll be helping out Suzanne Daniels (Microsoft Dev PMM) with <a href=\"https://meetupgemist.nl/\" rel=\"noopener\">meetupgemist.nl</a>.</p><p>The recording is not available yet, but here is <a href=\"https://mybuild.microsoft.com/sessions/0063cc6a-b3f9-439f-b67a-ccf3dbe11b59\" rel=\"noopener\">the session page</a>.</p><h2 id=\"creativity\"><a href=\"#creativity\" class=\"heading-anchor\">Creativity</a></h2><p>My other contributions to MSBuild was not planned upfront at all. They just happened throughout the event.</p><h3 id=\"digital-swag\"><a href=\"#digital-swag\" class=\"heading-anchor\">Digital Swag</a></h3><p>On the first day of MSBuild, just before it started, I read <a href=\"https://twitter.com/livelovegeek/status/1262849878943653889\" rel=\"noopener\">this tweet</a> by Morgan Mitchell who announced <a href=\"https://github.com/microsoft/Build2020_DigitalSwag\" rel=\"noopener\">this GitHub repo</a> with digital swag for MSBuild. I really liked the desktop wallpapers and decided to make an <a href=\"https://twitter.com/marcduiker/status/1262719249203597312\" rel=\"noopener\">8-bit version</a> for one of the designs (see the header image of this post). I made a pull request, which got merged very quick, and my first creative contribution to MSBuild was a fact! 😀</p><h3 id=\"pixel-art-portraits\"><a href=\"#pixel-art-portraits\" class=\"heading-anchor\">Pixel art portraits</a></h3><p>By the end of day one, I wanted to do a funny take on my highlight of that day. And since I LOL’d about Scott Gu his appearance in pyjama pants and dino slippers, I decided to make an 8-bit version of that and <a href=\"https://twitter.com/marcduiker/status/1262851019865763843?s=20\" rel=\"noopener\">posted it on Twitter</a>. It got some likes but nothing crazy.</p><p>During the next day, I thought it would be nice to show my appreciation to the hosts, because I know from experience it is a demanding task. I created 16x16 pixel portraits for them. I started with the UK/EMEA hosts of that day, Asim Hussain and Simona Cotin, and <a href=\"(https://twitter.com/marcduiker/status/1263065949164441601?s=20)\">posted that on Twitter</a>. This got quite some traction, and it became clear to me that I should make more of these, so all the hosts would have one. I continued made pixel portraits for:</p><ul class=\"list\"><li><a href=\"https://twitter.com/marcduiker/status/1263123133843808258?s=20\" rel=\"noopener\">Christina Warren</a> (US)</li><li><a href=\"https://twitter.com/marcduiker/status/1263185437226647553?s=20\" rel=\"noopener\">Seth Juarez &amp; Don Sarkar</a> (US)</li><li><a href=\"https://twitter.com/marcduiker/status/1263410707460218880?s=20\" rel=\"noopener\">Amy Kate Boyd &amp; Dean Bryen</a> (UK/EMEA)</li><li><a href=\"https://twitter.com/marcduiker/status/1263436663553916928?s=20\" rel=\"noopener\">Sonia Cuff, Aaron Powell &amp; Rick Claus</a> (APAC)</li><li><a href=\"https://twitter.com/marcduiker/status/1263454743407529984?s=20\" rel=\"noopener\">A quokka</a> 🤷‍♀️</li></ul><p>I received lovely responses from everyone 😊. The Microsoft Developer Twitter account did a shout-out with an illustrated &amp; 8-bit version of myself 😂.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/41.3.illustrated_marc-440w.webp 440w, /assets/images/41.3.illustrated_marc-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/41.3.illustrated_marc-650w.webp\" width=\"650\" height=\"550\" alt=\"Hilarious shout-out from the Microsoft Developer Twitter account\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Hilarious shout-out from the Microsoft Developer Twitter account.</em></p><p>During the final minutes of Build, I even got a shout-out from Dona, Seth, and Christina for this. This really made my day! 🎉😃</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/41.4.twitter_thankyou_message-440w.webp 440w, /assets/images/41.4.twitter_thankyou_message-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/41.4.twitter_thankyou_message-650w.webp\" width=\"650\" height=\"879\" alt=\"My thank you tweet with the 8-bit collage\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>My thank you tweet with the 8-bit collage.</em></p><p>I receive quite some requests for 8-bit portraits now 😅. I hope you understand I can’t do all of them. I make them by hand, pixel by pixel (using a tool called Aseprite). It takes quite some time to create a recognizable pixel portrait for someone. If you want to support my creative work, please buy me a coffee: <a href=\"https://ko-fi.com/marcduiker\" rel=\"noopener\">ko-fi.com/marcduiker</a> and let’s get in touch 😊.</p><h3 id=\"retro-game\"><a href=\"#retro-game\" class=\"heading-anchor\">Retro game</a></h3><p>I really enjoyed one of the last sessions of the third day titled <em>Inspiring Next Gen Coders with Make Code</em> (<a href=\"https://mybuild.microsoft.com/sessions/a1638103-16a8-4059-90ac-54c7e0dda8a2?source=sessions\" rel=\"noopener\">session INT157C</a>). Scott Hanselman and Louanne Murphy demonstrated <em>Make Code</em>, an open educational programming platform backed by various organizations. It allows you to create games or do IoT projects with hardware components from micro:bit and Adafruit, for instance. Programming is done in the browser with visual blocks or in Javascript. Since I was really eager to try <em>Make Code</em>, I decided to create a small retro style game using <a href=\"https://arcade.makecode.com\" rel=\"noopener\">arcade.makecode.com</a>.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/41.5.cheez-it_deth_match_game-440w.webp 440w, /assets/images/41.5.cheez-it_deth_match_game-650w.webp 650w, /assets/images/41.5.cheez-it_deth_match_game-960w.webp 960w, /assets/images/41.5.cheez-it_deth_match_game-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/41.5.cheez-it_deth_match_game-1200w.webp\" width=\"1200\" height=\"610\" alt=\"The Cheez-It Deth Match game\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>The Cheez-It Deth Match game</em></p><p>I only used the visual blocks and the built-in sprite editor to make this game called <em>Cheez-It Deth Match</em>. The game refers to a social hour during MSBuild day 2, where Dona Sarkar and Seth Juarez were ‘cooking’ Cheez-Its 🤔. <em>Deth</em> refers to the combination of Dona and Seth (<a href=\"https://twitter.com/donasarkar/status/1263305140200632321\" rel=\"noopener\">see this tweet</a>).</p><p>It’s a local two-player game, best played with a keyboard. One player plays Dona, and the other Seth and whoever eats the most Cheez-Its wins. Play it online at <a href=\"https://makecode.com/_CPJKJtiR5dz4\" rel=\"noopener\">makecode.com</a> 🕹. The game is definitely not perfect, but I built it in just 1.5 hours! The development &amp; playing experience is incredibly smooth and fun.</p><h2 id=\"build-complete\"><a href=\"#build-complete\" class=\"heading-anchor\">Build Complete</a></h2><p>Due to all the interactions (live and on Twitter) with the hosts and attendees, I really felt part of the event, even part of the team. I had so much fun during my speaking opportunities, and I really hope it helped and inspired other developers. ❤️</p><p>I’m also thrilled my creative contributions were so well received, and I hope it made the event even more engaging and fun. I’m looking forward to the next MSBuild!</p><p>Cheers,</p><p>Marc</p>",
      "date_published": "2020-05-22T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/serverlessdays-amsterdam-post-mortem/",
      "url": "https://marcduiker.dev/articles/serverlessdays-amsterdam-post-mortem/",
      "title": "ServerlessDays Amsterdam, a personal post-mortem",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.00-desk-440w.webp 440w, /assets/images/40.00-desk-650w.webp 650w, /assets/images/40.00-desk-960w.webp 960w, /assets/images/40.00-desk-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.00-desk-1200w.webp\" width=\"1200\" height=\"675\" alt=\"My desk at the start of the conference\" loading=\"lazy\" decoding=\"async\"></picture></p><p>It is now May 12th, four days after the ServerlessDays Amsterdam 2020 virtual conference. The conference was incredibly fun and slightly terrifying at the same time.</p><p>I had two roles during the conference; in the morning, I would do the hosting with Lian Li (who really has a talent for MC-ing), and during the entire day, I would be responsible for the technical part. In specific that means that I’d be calling in speakers &amp; panelists into the Skype group call, and controlling the streaming software (OBS), switching scenes, configuring webcam sources, shared screens, names, and starting the prerecorded sessions.</p><p>I gained some practice with live streaming over the last month since we did two virtual ServerlessDays Meetups. Still, I knew the conference would be a next-level challenge, and it sure was 😅. So I spent most of my free time over the last three weeks on perfecting my streaming setup and preparing a detailed script that I could use during the conference.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.01-script-440w.webp 440w, /assets/images/40.01-script-650w.webp 650w, /assets/images/40.01-script-960w.webp 960w, /assets/images/40.01-script-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.01-script-1200w.webp\" width=\"1200\" height=\"687\" alt=\"First few lines of the script for the conference\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"may-7th\"><a href=\"#may-7th\" class=\"heading-anchor\">May 7th</a></h2><p>I want to highlight some of the work I did before the conference day because this saved me quite some time and effort on the day itself.</p><h3 id=\"prerecorded-sessions\"><a href=\"#prerecorded-sessions\" class=\"heading-anchor\">Prerecorded sessions</a></h3><p>Since half of the speakers provided us with a prerecorded session (thank you!), I put those recordings in a VLC Media Source playlist in OBS with 5-second bumpers in between. When a new recorded session would start, I only needed to remove the first recording from the playlist, and I was good to go.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.02-vlc-media-source-440w.webp 440w, /assets/images/40.02-vlc-media-source-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/40.02-vlc-media-source-650w.webp\" width=\"650\" height=\"253\" alt=\"The VLC Media source in OBS with all the prerecorded videos\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>The VLC Media source in OBS with all the prerecorded videos.</em></p><p>An annoying thing about the VLC Media Source (and the regular Media Source) is that you don’t see the remaining playtime of the video. Having these times is essential if you want to give a heads up to the hosts, and speaker. To fix this, I added timers for each prerecorded session to my StreamDeck. So when I switched to the OBS scene with the VLC Media Source, I would also press the corresponding timer on the StreamDeck, which started a countdown.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.03-streamdeck-timers-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/40.03-streamdeck-timers-440w.webp\" width=\"440\" height=\"343\" alt=\"Using timers for each recorded session\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Using timers for each recorded session.</em></p><h3 id=\"text-files-and-chatlog-mode\"><a href=\"#text-files-and-chatlog-mode\" class=\"heading-anchor\">Text Files &amp; Chatlog Mode</a></h3><p>Something I didn’t want to spend much time on during the day itself would be configuring the speaker’s names in OBS. I was looking into the read from file option, and then I noticed the Chatlog Mode. With this option enabled, with a row count of 1, only the last line is used as the input for a text field. So I put all the speaker names in a text file in reverse order and used that as the source for the speaker name field in OBS. When a new speaker was joining, I only needed to remove the last line and save the file.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.04-chatlogmode-440w.webp 440w, /assets/images/40.04-chatlogmode-650w.webp 650w, /assets/images/40.04-chatlogmode-960w.webp 960w, /assets/images/40.04-chatlogmode-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.04-chatlogmode-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Using text files with chatlog mode enabled\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Using text files with chatlog mode enabled.</em></p><h2 id=\"may-8th-the-conference-day\"><a href=\"#may-8th-the-conference-day\" class=\"heading-anchor\">May 8th, the conference day</a></h2><h3 id=\"700\"><a href=\"#700\" class=\"heading-anchor\">7:00</a></h3><p>My day started at a reasonable time. I woke up at 7:00, took a shower, had some breakfast, and made <em>a lot of coffee</em> ☕. I also prepared some lunch, snacks, and fruits to take with me upstairs since I would spend my entire day in my home office in the attic. I did allow myself some bathroom breaks though.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.05-food-440w.webp 440w, /assets/images/40.05-food-650w.webp 650w, /assets/images/40.05-food-960w.webp 960w, /assets/images/40.05-food-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.05-food-1200w.webp\" width=\"1200\" height=\"900\" alt=\"Refreshments for during the day\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Refreshments for during the day.</em></p><h3 id=\"800-am\"><a href=\"#800-am\" class=\"heading-anchor\">8:00 AM</a></h3><p>Once settled behind my laptop, I RDP-ed to the virtual machine in Azure, which is the heart of the streaming setup. It’s an NV6 machine, GPU enabled, and with enough CPU and memory to use for streaming.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.06-obs-440w.webp 440w, /assets/images/40.06-obs-650w.webp 650w, /assets/images/40.06-obs-960w.webp 960w, /assets/images/40.06-obs-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.06-obs-1200w.webp\" width=\"1200\" height=\"650\" alt=\"The OBS setup running in an Azure VM\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>The OBS setup running in an Azure VM.</em></p><p>Since we’re using a combination of Skype, NDI Tools, and OBS, I started a Skype group call on the VM and invited myself, so I call in from my laptop.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.07-skype-group-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/40.07-skype-group-440w.webp\" width=\"440\" height=\"494\" alt=\"The Skype group call\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>The Skype group call.</em></p><p>I verified the webcam source in OBS since I was also doing a hosting role in the morning. I once more checked the order of the speaker names in the text file, and the prerecorded sessions in the playlist. I also checked the scene transitions using my StreamDeck since I updated the software earlier this week (risky, I know).</p><h3 id=\"830\"><a href=\"#830\" class=\"heading-anchor\">8:30</a></h3><p>I invited Lian to the group call and asked her to share her screen so I could set up the sources in OBS.</p><h3 id=\"845\"><a href=\"#845\" class=\"heading-anchor\">8:45</a></h3><p>Our keynote speaker, Ant Stanley, joined the group call. We three had a nice and relaxed chat while I was setting up the sources in OBS. Ant was going to give his session live, so we needed to make sure screen sharing was working. I added his webcam feed to the screen sharing scene so the audience could see his face during his session (more on this later). Lian quickly asked Ant for a fun fact she could use for his intro.</p><h3 id=\"850\"><a href=\"#850\" class=\"heading-anchor\">8:50</a></h3><p>I started the 10 min countdown timer and started streaming. We were streaming simultaneously to YouTube, Twitch, and Periscope (using Restream). There were already some people waiting on YouTube, amazing!</p><h3 id=\"900\"><a href=\"#900\" class=\"heading-anchor\">9:00</a></h3><p>Just before 9:00, I started counting down out loud so Lian would know when the exact moment of go-live would be. As soon as the timer reached 0:00, I switched to the scene with webcam feeds of Lian and me, and we are <a href=\"https://www.youtube.com/watch?v=cqaewpYtYTA&amp;t=595s\" rel=\"noopener\">LIVE</a> 🎉. Lian did most of the intro work here, as agreed upfront, and she did great! There was an issue with Lian her webcam feed, unfortunately. The entire frame was resizing now and then. The resizing kept on occurring during the whole day and also happened to a few others. I don’t know the cause yet, could be Skype, NDI Tools, or the OBS plugin. I was quite distracted by this and tried to rescale the webcam source whenever this happened, it felt like a continuous battle 😟.</p><ul class=\"list\"><li>Issue 1: Uncontrolled resizing of webcam/NDI source.</li><li>Solution: <s>unknown</s>, – [UPDATE May 14th] –<br>Thanks to the Twitterverse I now know the source of the issue and the solution! NDI Tools scales the video feed based on available bandwidth. Apparently everyone knew this but me! 😅 Thanks to <a href=\"https://twitter.com/maartenballiauw\" rel=\"noopener\">Maarten Balliauw</a> and <a href=\"https://twitter.com/hboelman\" rel=\"noopener\">Henk Boelman</a>, who already <a href=\"https://www.henkboelman.com/articles/online-meetups-with-obs-and-skype/\" rel=\"noopener\">wrote about this</a>. The fix is to apply a Transform in OBS to prevent the scaling as is <a href=\"https://support.skype.com/en/faq/FA34853/what-is-skype-for-content-creators\" rel=\"noopener\">described here</a>. Note to self: RTFM! 😂</li></ul><h3 id=\"915\"><a href=\"#915\" class=\"heading-anchor\">9:15</a></h3><p>Ant started his keynote session, and I switched to the scene with his shared screen and his webcam feed. It became apparent quickly that there was not a single good spot for this small webcam inside the shared screen feed since Ant’s slides were ever-changing. I moved his webcam feed around during this presentation, which must have looked hilarious to the audience 😂.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.08-ant-440w.webp 440w, /assets/images/40.08-ant-650w.webp 650w, /assets/images/40.08-ant-960w.webp 960w, /assets/images/40.08-ant-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.08-ant-1200w.webp\" width=\"1200\" height=\"674\" alt=\"Ant being moved from right to left during his presentation\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Ant being moved from right to left during his live presentation.</em></p><ul class=\"list\"><li>Issue 2: Webcam source hides info on slides.</li><li>Solution: Don’t compose a scene where the webcam source is overlaying the presentation source. Or instruct speakers to leave some space available for their webcam feed.</li></ul><h3 id=\"950-1215\"><a href=\"#950-1215\" class=\"heading-anchor\">9:50-12:15</a></h3><p>After Ant’s keynote, there were three prerecorded sessions, each with live Q&amp;A from Tomasz Konieczny, Riccardo Mocchetti, and Aymen Chetoui. About five minutes before each session, I would add the speaker to the Skype group call, set up their webcam source, update the VLC Media Source playlist, and update the speakers.txt file. All these sessions went quite smoothly. A live ten-minute lightning session with Ebru Cucen followed without me requiring to move around her webcam source all the time, thank you, Ebru! 😄</p><h3 id=\"1215\"><a href=\"#1215\" class=\"heading-anchor\">12:15</a></h3><p>Just before 12:15, the members of the expert panel joined the group call. I set up their webcams/NDI sources in the scene called Panel OBS. This scene was new and hadn’t been tested during any of the meetups. And this became quite evident since I forgot to mute the panelists’ audio sources, which resulted in a loud and annoying echo (sorry for your ears!). I received multiple warnings from the team and attendees, so this was fixed relatively quickly. It could have easily been prevented if I had tried the scene with the four panelists in advance. Besides the audio issue, the resizing issue was also prominent in this scene, for both Lian and Sara’s webcams. The panel discussion itself went very well, but due to the technical issues, I was too distracted to follow it closely.</p><ul class=\"list\"><li>Issue 3: Echo due to multiple audio sources in an untested scene.</li><li>Solution: Always do a trial run with scenes that use multiple NDI sources.</li></ul><h3 id=\"1240\"><a href=\"#1240\" class=\"heading-anchor\">12:40</a></h3><p>Time for the lunch break! Even though I had prepared my lunch up-front and took it with me upstairs, I really enjoyed being able to have a small break. At this moment, I was still annoyed with the weird resizing webcam issues, so it was good to have a short break for my laptop. Lian dropped out of the call because we would now switch over to Floor Drees and Marek Kuczynski as the hosts. I invited them to the group call, set up their webcam sources, and updated the hosts’ names in OBS to match theirs. I also added the next speaker, Sven Al Hamad, to the group call and configured his prerecorded session and name. So far, so good.</p><h3 id=\"1320\"><a href=\"#1320\" class=\"heading-anchor\">13:20</a></h3><p>When the break was almost over, I counted down out loud again, so Floor and Marek would know the exact moment of go live. I LOL’d about the introduction Floor gave when she mentioned that Marek would explain why they are ‘arch enemies’ 🤣. Floor works at Microsoft and Marek at AWS. I love the fact that they could host together, it shows we have a fun and respectful community 🧡.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.08-floorandmarek-440w.webp 440w, /assets/images/40.08-floorandmarek-650w.webp 650w, /assets/images/40.08-floorandmarek-960w.webp 960w, /assets/images/40.08-floorandmarek-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.08-floorandmarek-1200w.webp\" width=\"1200\" height=\"674\" alt=\"Hilarious intro by Floor and Marek\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Hilarious intro by Floor and Marek.</em></p><h3 id=\"1320-1525\"><a href=\"#1320-1525\" class=\"heading-anchor\">13:20 - 15:25</a></h3><p>The next three sessions from Sven Al Hamad, Josh Carlise, and Farrah Campbell were all prerecorded and went pretty smoothly, at least from the perspective of the audience. My Skype call actually dropped during the prerecorded session by Sven. It took me about 1 minute to reconnect again, nobody noticed, but this freaked me out quite a bit! 😱</p><ul class=\"list\"><li>Issue 4: Skype call dropped unexpectedly.</li><li>Solution: Don’t use Skype for events that last several hours?</li></ul><p>At the start of Josh his session, I was a bit too slow with updating the speaker name source in OBS. Also, Josh’s audio was not working initially, but it was all fixed quickly. His session ended pretty hilariously thanks to a question from Floor; “What is behind door number 3?”. It was a bathroom, and Josh said he didn’t want to open the door in case someone would be in there 🤣.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.09-doorno3-440w.webp 440w, /assets/images/40.09-doorno3-650w.webp 650w, /assets/images/40.09-doorno3-960w.webp 960w, /assets/images/40.09-doorno3-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.09-doorno3-1200w.webp\" width=\"1200\" height=\"674\" alt=\"Josh pointing to door no 3\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Josh pointing to door no 3.</em></p><h3 id=\"1525-1625\"><a href=\"#1525-1625\" class=\"heading-anchor\">15:25 - 16:25</a></h3><p>Two live sessions followed; one by Sia Ghassemi, who showed some awesome tricks with Excel and Azure Functions, and one lightning session by Sebastien Goasguen about AWS EventBridge. Both went well regarding the tech, no weird issues with resizing webcam feeds or placement of the webcam on the presentations, woohoo! The success didn’t last long though…</p><p>During the lightning session, the panelists joined the group call again for the expert panel session, but two of the three webcam feeds were not showing in OBS! I removed the non-working NDI sources from the scene and added them again, now it did work again, phew. But of course, I forgot something which would result in more issues…</p><ul class=\"list\"><li>Issue 5: Webcam feeds not available for people rejoining the call.</li><li>Solution/Workaround: Remove NDI sources and add them again.</li></ul><h3 id=\"1625-1730\"><a href=\"#1625-1730\" class=\"heading-anchor\">16:25 - 17:30</a></h3><p>As soon as the second expert panel session started, the annoying echo was back! When I added the NDI sources for the panelists again, I forgot to mute their corresponding audio sources, aarrgh! I did fix it a bit quicker than the first panel session, I think.</p><p>Near the end of the panel discussion, I added our closing keynote speaker to the call, Simona Cotin. I was a bit late with switching to the scene with just two webcam feeds for Floor and Marek. So while the panelists were leaving the group call, their scene was still active, and empty rectangles were appearing where their webcam used to be. Not the most elegant. Then I switched to the scene with both hosts and Simona, and then Floor her webcam feed started resizing out of the blue. Luckily during Simona her session, everything went smoothly again. The keynote was very well done, both in style and content.</p><ul class=\"list\"><li>Issue 6: Empty spots in the scene due to people dropping out of the call.</li><li>Solution: Agree upfront that people will stay in the group call a bit longer with webcam enabled.</li></ul><p>During the final keynote session, I created a new scene in OBS with the webcam feeds of all four hosts, so Floor, Marek, Lian, and myself. I wanted to have a moment where we would all be visible to the audience so I could say thanks for their excellent work.</p><h3 id=\"1730-1755\"><a href=\"#1730-1755\" class=\"heading-anchor\">17:30 - 17:55</a></h3><p>After Simona’s keynote, I switched to the new scene, and again two of the four webcam sources had resizing issues. I pretty much gave up by now to correct it. 😫</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.10-4hosts-440w.webp 440w, /assets/images/40.10-4hosts-650w.webp 650w, /assets/images/40.10-4hosts-960w.webp 960w, /assets/images/40.10-4hosts-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.10-4hosts-1200w.webp\" width=\"1200\" height=\"674\" alt=\"Unexpected resizing of webcam feeds again\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Unexpected resizing of webcam feeds again.</em></p><p>After thanking Floor and Marek, I switched to the scene with just Lian and me so we could close the conference. Again this scene suffered from webcam feed resizing now and then.<br>When it was time to raffle the prizes, the Google Sheets script for selecting the winners did not cooperate, it took way longer than we expected. We spent quite some time online waiting for the script to finish, and Lian did a great job of filling the void with her creativity. Also, Floor helped by asking questions to keep the conversation going, great teamwork! In the end, we decided not to wait for the script to finish and announce the winners on Discord.</p><ul class=\"list\"><li>Issue 7: Script to select winners was taking too long.</li><li>Solution: Always have a backup plan when doing a live raffle of prizes.</li></ul><p>Lian continued the closing of the conference and thanked sponsors, speakers, and organizers. At the exact moment, Lian was thanking me, my Skype connection froze, and eventually, my call dropped again! I was really was annoyed since this was the second time it happened during the day. I had to replay the YouTube recording to hear Lian’s compliments, so thank you, Lian, for your kind words. I was lucky my frozen image was looking ok and I’m not making a weird face 😅.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.11-frozen-440w.webp 440w, /assets/images/40.11-frozen-650w.webp 650w, /assets/images/40.11-frozen-960w.webp 960w, /assets/images/40.11-frozen-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.11-frozen-1200w.webp\" width=\"1200\" height=\"674\" alt=\"My frozen but happy image during the close out of the conference\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>My frozen but happy image during the close out of the conference.</em></p><ul class=\"list\"><li>Issue 8: Skype call dropped unexpectedly again.</li><li>Solution: Never use Skype again for an event that lasts an entire day! 😞</li></ul><p>After I rejoined the call, Lian &amp; I continued our conversation for a bit, and we encouraged everyone to attend the after-party event by Sam Aaron.</p><h3 id=\"1755\"><a href=\"#1755\" class=\"heading-anchor\">17:55</a></h3><p>As soon as I dropped off the group call, I stopped streaming the conference event and got in contact with Sam via Discord to test the stream for the after-party. I was using another Restream account for the after-party, which I had configured earlier that week; unfortunately, I had to reconnect the Twitch account in Restream. Next, the ServerlessDays after-party event on YouTube was not showing Sam’s live stream. I had to create a new YouTube event via Restream, and Sam had to stop and start streaming to get the live streaming to work again.</p><ul class=\"list\"><li>Issue 9: Connection issues with streaming and very little time to fix it.</li><li>Solution: Perform a live test at least a day in advance. Use a paid version for Restream, which allows multiple simultaneous streams and better options for restarting streams.</li></ul><h3 id=\"1810\"><a href=\"#1810\" class=\"heading-anchor\">18:10</a></h3><p>Once both Twitch and YouTube streams were up and running, which was 10 minutes later than planned, I could finally relax, grab a beer, and enjoy Sam’s performance.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.12-afterparty-440w.webp 440w, /assets/images/40.12-afterparty-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/40.12-afterparty-650w.webp\" width=\"650\" height=\"866\" alt=\"Me enjoying the afterparty!\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Me enjoying the afterparty!</em></p><p>The performance was awesome! Sam was clearly having a good time as well since we could see hem dancing while coding his music. I had prepared a short audio recording where I welcome everyone to the after-party. During the performance, I sent it to Sam, hoping that he would use it, and he did!</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/8RX2WkBiXk0\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><p>Once the after-party was done, we thanked Sam on Discord. There was plenty of positive feedback there, which was great to see.</p><p>Once the after-party was over, I watched some Netflix, and then went to bed. I couldn’t fall asleep, though, as I still felt the adrenaline rushing through my body. When I did fell asleep, I probably dreamt of continuously resizing webcam feeds in OBS 😫.</p><h2 id=\"may-9th\"><a href=\"#may-9th\" class=\"heading-anchor\">May 9th</a></h2><p>When I looked at the Restream statistics the day after, there were, on average, 82 people watching simultaneously. This is a bit lower than I expected since we received 380 registrations for the event. A lot of people must have thought the recording would appear online anyway. Or perhaps they had an online conference overload. Honestly, I can understand that. This was a free conference in the end, so some virtual no-show was expected.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/40.13-restream-440w.webp 440w, /assets/images/40.13-restream-650w.webp 650w, /assets/images/40.13-restream-960w.webp 960w, /assets/images/40.13-restream-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/40.13-restream-1200w.webp\" width=\"1200\" height=\"613\" alt=\"Restream statistics showing the view count throughout the day\" loading=\"lazy\" decoding=\"async\"></picture></p><p><em>Restream statistics showing the view count throughout the day.</em></p><p>You can clearly see a drop around lunchtime (the chart is in UTC) and a slow decline towards the afternoon, as is expected for any conference. Most of the viewers watched on YouTube (61.1%), followed by Twitch (38.9%). Apparently, nobody viewed the event on Periscope/Twitter, so I’ll be dropping that channel for future streaming sessions.</p><h2 id=\"may-12th\"><a href=\"#may-12th\" class=\"heading-anchor\">May 12th</a></h2><p>The YouTube viewcount as of today is 775. So the views are steadily increasing, which is great!</p><p>Yesterday, Lian sent out a questionnaire asking for feedback, regarding both the format and the content. I hope we will receive enough information so we can make an even better conference next year 😊.</p><h2 id=\"until-next-time\"><a href=\"#until-next-time\" class=\"heading-anchor\">Until next time?</a></h2><p>If you’ve read until this point, you’re either really interested in this or slightly masochistic. If you want to see all the mistakes for yourself, please enjoy the recording of the conference on YouTube. I do suggest you use the links in the description of the video so you can skip ahead to the individual sessions.</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/cqaewpYtYTA\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><p>So, would I ever do this again? Yes and no. I would do this again since I enjoy working with the kind people in this awesome community. I also like to play around with the technology and learn new things. I would not do it in the exact same way, though. I’d prepare more, investigate in different tools, and, most importantly, share the responsibility for running the technical setup. Because now, I was solely responsible for doing tech work, which was quite a risk. If I had fallen down the stairs and had broken my leg, nobody could have stepped in quickly and continued the streaming. So my most important lesson learned is to involve others early on and to help them get familiar with the technical setup. This way, we can share the responsibility and I can sleep better in the nights leading to the event 😉.</p><p>I hope to see you all at the conference next year, or at one of our <a href=\"https://www.meetup.com/ServerlessDays-Amsterdam/\" rel=\"noopener\">(virtual) Meetups</a>!</p><p><em>Marc</em></p>",
      "date_published": "2020-05-12T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/streaming-for-serverlessdays/",
      "url": "https://marcduiker.dev/articles/streaming-for-serverlessdays/",
      "title": "Live streaming the meetups and conference for ServerlessDays Amsterdam - Part 1",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/39.1.cover-440w.webp 440w, /assets/images/39.1.cover-591w.webp 591w\" sizes=\"90vw\"><img src=\"/assets/images/39.1.cover-591w.webp\" width=\"591\" height=\"442\" alt=\"StreamDeck\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"everyone-is-live-streaming\"><a href=\"#everyone-is-live-streaming\" class=\"heading-anchor\">Everyone is live streaming!</a></h2><p>Well, ok, not <em>everyone</em>, but a lot of meetups &amp; conferences which were previously only offline and IRL have an online presence these days. It really lowers the boundary of attending these events, and I think this is great!</p><p>ServerlessDays Amsterdam (which I’m co-organizing) is also moving online for both the meetups and the conference. In this post, I’m highlighting some details on how we’re setting this up.</p><h2 id=\"streaming-setup-basics\"><a href=\"#streaming-setup-basics\" class=\"heading-anchor\">Streaming setup basics</a></h2><p>I was already using <a href=\"https://streamlabs.com/streamlabs-obs\" rel=\"noopener\">StreamLabs OBS (SLOBS)</a> and regular <a href=\"https://obsproject.com/\" rel=\"noopener\">OBS</a> for my vlog. I like the level of control these tools offer, and yes, that comes with a steeper learning curve compared to using online conferencing tools like Zoom/Jitsi.</p><p>A lot of things have been written recently on how to set up a live streaming/recording system with OBS. So I’m just going to refer to the blogs which I’ve read and highly recommend:</p><ul class=\"list\"><li><a href=\"https://www.hanselman.com/blog/TakeRemoteWorkerEducatorWebcamVideoCallsToTheNextLevelWithOBSNDIToolsAndElgatoStreamDeck.aspx\" rel=\"noopener\">Take Remote Worker/Educator webcam video calls to the next level with OBS, NDI Tools, and Elgato Stream Deck - Scott Hanselman</a></li><li><a href=\"https://www.henkboelman.com/articles/online-meetups-with-obs-and-skype/\" rel=\"noopener\">Online meetups with OBS and Skype - Henk Boelman</a></li><li><a href=\"https://blog.maartenballiauw.be/post/2020/04/02/streaming-a-community-event-on-youtube-sharing-the-technologies-and-learnings-from-virtual-azure-community-day.html\" rel=\"noopener\">Streaming a Community Event on YouTube - Sharing the Technologies and Learnings from Virtual Azure Community Day - Maarten Balliauw</a></li></ul><blockquote><p>Special thanks goes out to Cloud Advocate Henk Boelman, who gave me detailed advice of using a virtual machine (VM) in Azure in combination with Skype, NDI Tools, and OBS. It works like a charm! ❤️</p></blockquote><p>Having a dedicated VM in the cloud for streaming has quite some benefits over using my local laptop:</p><ul class=\"list\"><li>I can get a VM which is much more powerful than my laptop.</li><li>I can share the VM with others.</li><li>My internet connection is no longer the bottleneck for the live stream.</li></ul><p>Are there downsides to using a VM? Yes, sure, the most obvious one is the price. You have to pay for a VM. The larger the VM, the more you pay. I’m lucky that I have Azure credits, thanks to being an Azure MVP. But even if I didn’t have these credits, I would still do it. This VM is only used for a couple of hours each month. So I’m never paying the full month price, just a fraction of it.</p><h2 id=\"specific-setup-for-serverlessdays-amsterdam\"><a href=\"#specific-setup-for-serverlessdays-amsterdam\" class=\"heading-anchor\">Specific setup for ServerlessDays Amsterdam</a></h2><p>For Serverless Amsterdam I ended up provisioning a GPU enabled virtual machine (Win 10 OS) on Azure, size <em>Standard NV6</em>, with the following specs:</p><ul class=\"list\"><li>6 virtual CPUs</li><li>56 GiB memory</li><li>1 GPU</li><li>8 GiB GPU memory</li></ul><h3 id=\"enable-the-gpu-utilisation\"><a href=\"#enable-the-gpu-utilisation\" class=\"heading-anchor\">Enable the GPU utilisation</a></h3><p>What I didn’t realize initially is that the VM has no preinstalled drivers to actually use the GPU. So make sure to install the NVIDIA GPU drivers. I did that by installing the <code>NVIDIA GPU Driver Extension</code> though the Extensions blade in the Azure Portal:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/39.2.nvidia_driver_extensions-440w.webp 440w, /assets/images/39.2.nvidia_driver_extensions-650w.webp 650w, /assets/images/39.2.nvidia_driver_extensions-960w.webp 960w, /assets/images/39.2.nvidia_driver_extensions-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/39.2.nvidia_driver_extensions-1200w.webp\" width=\"1200\" height=\"664\" alt=\"Azure Portal Extensions\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Now we can use the hardware encoding (NVENC) in OBS:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/39.3.obs_output-440w.webp 440w, /assets/images/39.3.obs_output-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/39.3.obs_output-650w.webp\" width=\"650\" height=\"454\" alt=\"OBS Output Settings\" loading=\"lazy\" decoding=\"async\"></picture></p><h3 id=\"redirect-local-usb-devices-to-the-vm\"><a href=\"#redirect-local-usb-devices-to-the-vm\" class=\"heading-anchor\">Redirect local USB devices to the VM</a></h3><p>I purchased an <a href=\"https://www.elgato.com/en/gaming/stream-deck\" rel=\"noopener\">Elgato Stream Deck</a> so I can easily switch between scenes in OBS and set timers. I really like this small piece of hardware since it’s very customizable.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/39.4.tweet_stream_deck-440w.webp 440w, /assets/images/39.4.tweet_stream_deck-591w.webp 591w\" sizes=\"90vw\"><img src=\"/assets/images/39.4.tweet_stream_deck-591w.webp\" width=\"591\" height=\"442\" alt=\"Twitter Stream Deck Tweet\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The Stream Deck can be used OOTB when OBS is running locally but I’d also want to use this device in OBS running in the VM. However local USB devices are not connected by default when a remote desktop session is used. Lucky we can use a technique called <a href=\"https://techcommunity.microsoft.com/t5/enterprise-mobility-security/introducing-microsoft-remotefx-usb-redirection-part-1/ba-p/247035\" rel=\"noopener\">RemoteFX USB redirection</a> which is enabled by changing two group policies, one for the local machine (client) and one for the remote VM (host).</p><h4 id=\"group-policy-setting-for-the-local-machine\"><a href=\"#group-policy-setting-for-the-local-machine\" class=\"heading-anchor\">Group Policy Setting for the Local Machine</a></h4><ul class=\"list\"><li>Open the <em>Local Group Policy Editor</em> (use the windows search bar and type <code>group</code> )</li><li>Navigate to <em>Administrative Templates</em> &gt; <em>Windows Components</em> &gt; <em>Remote Desktop Services</em> &gt; <em>Remote Desktop Connection Client</em> &gt; <em>RemoteFX USB Device Redirection</em></li><li>Set <em>Allow RDP redirection of other supported RemoteFX USB devices from this computer</em> to <code>Enabled</code>.</li><li>Select <code>Administrators &amp; Users</code> for the <em>RemoteFX USB Redirection Access Rights</em> options.</li></ul><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/39.5.rdp_client-440w.webp 440w, /assets/images/39.5.rdp_client-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/39.5.rdp_client-650w.webp\" width=\"650\" height=\"601\" alt=\"Group policy settings for enabling RemoteFX on the local client side\" loading=\"lazy\" decoding=\"async\"></picture></p><p>In a command prompt run: <code>gpupdate /force</code> to enfore the updated policy (a restart might be required as well).</p><h4 id=\"group-policy-setting-for-the-remote-vm\"><a href=\"#group-policy-setting-for-the-remote-vm\" class=\"heading-anchor\">Group Policy Setting for the remote VM</a></h4><ul class=\"list\"><li>Open the <em>Local Group Policy Editor</em> and navigate to <em>Administrative Templates</em> &gt; <em>Windows Components</em> &gt; <em>Remote Desktop Services</em> &gt; <em>Remote Desktop Session Host</em> &gt; <em>Device and Resource Redirection</em></li><li>Set the <em>Do not allow suported Plug and Play device redirection</em> to <code>Disabled</code>.</li></ul><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/39.1.cover-440w.webp 440w, /assets/images/39.1.cover-591w.webp 591w\" sizes=\"90vw\"><img src=\"/assets/images/39.1.cover-591w.webp\" width=\"591\" height=\"442\" alt=\"39.6.rdp_host.png\" loading=\"lazy\" decoding=\"async\"></picture></p><p>In a command prompt run: <code>gpupdate /force</code> to enfore the updated policy (a restart might be required as well).</p><blockquote><p>I might be stating the obvious, but the Stream Deck software/drivers also need to be installed on the VM.</p></blockquote><h4 id=\"starting-the-remote-desktop-connection\"><a href=\"#starting-the-remote-desktop-connection\" class=\"heading-anchor\">Starting the Remote Desktop Connection</a></h4><p>Now a remote desktop connection can be started from the local machine, and the USB connections can be selected, which are redirected to the VM.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/39.7.rdp_settings-440w.webp 440w, /assets/images/39.7.rdp_settings-650w.webp 650w, /assets/images/39.7.rdp_settings-872w.webp 872w\" sizes=\"90vw\"><img src=\"/assets/images/39.7.rdp_settings-872w.webp\" width=\"872\" height=\"520\" alt=\"Edit the RDP session settings to allow USB connectivity\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Now both hard and software are set up to use the Stream Deck on the remote VM.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/39.8.stream_deck_vm-440w.webp 440w, /assets/images/39.8.stream_deck_vm-572w.webp 572w\" sizes=\"90vw\"><img src=\"/assets/images/39.8.stream_deck_vm-572w.webp\" width=\"572\" height=\"443\" alt=\"Stream Deck software on the VM\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/39.9.stream_deck_hardware-440w.webp 440w, /assets/images/39.9.stream_deck_hardware-572w.webp 572w\" sizes=\"90vw\"><img src=\"/assets/images/39.9.stream_deck_hardware-572w.webp\" width=\"572\" height=\"405\" alt=\"Stream Deck hardware\" loading=\"lazy\" decoding=\"async\"></picture></p><p>In the next part I’ll show more details about the OBS setup we’re using for ServerlessDays Amsterdam.</p>",
      "date_published": "2020-04-08T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/durable-functions-api-durableorchestrationclient-4/",
      "url": "https://marcduiker.dev/articles/durable-functions-api-durableorchestrationclient-4/",
      "title": "Discovering the Durable Functions API - Human Interaction (DurableOrchestrationClient part 4)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/38.cover-440w.webp 440w, /assets/images/38.cover-650w.webp 650w, /assets/images/38.cover-960w.webp 960w, /assets/images/38.cover-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/38.cover-1200w.webp\" width=\"1200\" height=\"683\" alt=\"Human Interaction Pattern\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"human-interaction-pattern\"><a href=\"#human-interaction-pattern\" class=\"heading-anchor\">Human Interaction Pattern</a></h2><p>This is the fourth part of the Durable Functions series where I look into the <code>DurableOrchestrationClient</code>. In the video I’ll talk about the Human Interaction pattern, which deals with raising events from the client and waiting for events in the orchestrator.</p><p>Here’s the video, please give it a thumbs up if you like it and subscribe to my channel so you’ll be notified of new videos.</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/bJLuyzcTT78\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><h3 id=\"resources\"><a href=\"#resources\" class=\"heading-anchor\">Resources</a></h3><p>The source code that is used for this demo can be found on <a href=\"https://github.com/marcduiker/demos-azure-durable-functions\" rel=\"noopener\">GitHub</a>.</p><h3 id=\"links-to-other-posts-in-this-series\"><a href=\"#links-to-other-posts-in-this-series\" class=\"heading-anchor\">Links to other posts in this series</a></h3><ul class=\"list\"><li><a href=\"/articles/durable-functions-api-durableorchestrationclient-1\">Starting Orchestrations (DurableOrchestrationClient Part 1)</a></li><li><a href=\"/articles/durable-functions-api-durableorchestrationclient-2\">Retrieving the Orchestration Status (DurableOrchestrationClient Part 2)</a></li><li><a href=\"/articles/durable-functions-api-purge-terminate\">Purge &amp; Terminate Orchestrations (DurableOrchestrationClient Part 3)</a></li></ul>",
      "date_published": "2020-03-15T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/my-learnings-from-running-the-azure-functions-updates-twitterbot-for-half-a-year/",
      "url": "https://marcduiker.dev/articles/my-learnings-from-running-the-azure-functions-updates-twitterbot-for-half-a-year/",
      "title": "My learnings from running the Azure Functions Updates Twitterbot for half a year",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/37.1.azfunctionupdates_diagram_cover-440w.webp 440w, /assets/images/37.1.azfunctionupdates_diagram_cover-650w.webp 650w, /assets/images/37.1.azfunctionupdates_diagram_cover-960w.webp 960w, /assets/images/37.1.azfunctionupdates_diagram_cover-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/37.1.azfunctionupdates_diagram_cover-1200w.webp\" width=\"1200\" height=\"550\" alt=\"AzureFunctionsUpdate TwitterBot architecture\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"some-quick-facts-about-the-twitterbot\"><a href=\"#some-quick-facts-about-the-twitterbot\" class=\"heading-anchor\">Some quick facts about the Twitterbot</a></h2><p>In my <a href=\"/articles/creating-azure-functions-updates-twitterbot\">previous post</a>, I wrote about why and how I created the Azure Functions Updates Twitterbot. This bot has been posting updates about Azure Functions related GitHub repositories (and Azure Service announcements) since February 2019, so that’s well over 6 months. It monitors 24 repositories, including itself, and the Azure Service Updates RSS feed, filtered for Azure Functions related updates. The full list of sources is listed <a href=\"https://github.com/marcduiker/az-func-updates/blob/master/docs/monitored-sources.md\" rel=\"noopener\">on GitHub</a>. The function app that runs the bot has captured 128 GitHub releases, 8 Azure Service Update posts and posted 154 messages to Twitter (the introductory tweets were posted manually).</p><p>In this post, I want to highlight some of the actions I took and the insights I’ve got after I put the Twitterbot live. I’m going to cover: failure &amp; resiliency, monitoring &amp; alerts, performance, and costs.</p><h2 id=\"1-failure-and-resiliency\"><a href=\"#1-failure-and-resiliency\" class=\"heading-anchor\">1. Failure &amp; resiliency</a></h2><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/37.2.azfunctionupdates_diagram-440w.webp 440w, /assets/images/37.2.azfunctionupdates_diagram-650w.webp 650w, /assets/images/37.2.azfunctionupdates_diagram-960w.webp 960w, /assets/images/37.2.azfunctionupdates_diagram-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/37.2.azfunctionupdates_diagram-1200w.webp\" width=\"1200\" height=\"395\" alt=\"Azure Functions Updates component diagram\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Looking at the component diagram above, three external dependencies are shown; GitHub, Twitter and Azure Table Storage. <em>(Note that this is the initial design of the application, it has been extended later to include the Azure Service Updates RSS feed.)</em></p><p>Every service has some planned or unplanned downtime, so the function app has to cope with short service interruptions for its dependencies. Luckily the Durable Functions API provides a built-in mechanism to deal with this; it can perform retries on activity functions. Activity functions are called from the orchestrator function with the <em>CallActivityWithRetryAsync</em> method. Here’s an example where the <em>GetLatestReleaseFromGitHub</em> activity function is called:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// Get most recent release from GitHub</span>\ngetLatestReleaseFromGitHubTasks<span class=\"token punctuation\">.</span><span class=\"token function\">Add</span><span class=\"token punctuation\">(</span>context<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">CallActivityWithRetryAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>RepositoryRelease<span class=\"token punctuation\">&gt;</span></span></span><span class=\"token punctuation\">(</span>\n    <span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>GetLatestReleaseFromGitHub<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token function\">GetDefaultRetryOptions</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    repositoryConfiguration<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>The second argument in this method specifies the <em>RetryOptions</em>. With that, you can control how many times and with what frequency the activity function is retried when it fails. Since I reuse the same retry options at different places in the orchestrator, I have put it in a separate method:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">RetryOptions</span> <span class=\"token function\">GetDefaultRetryOptions</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">RetryOptions</span><span class=\"token punctuation\">(</span>TimeSpan<span class=\"token punctuation\">.</span><span class=\"token function\">FromMinutes</span><span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>Using the above <em>RetryOptions</em>, when an activity function fails, the Durable Functions framework retries the execution of the activity for a maximum of 3 times with an interval of 1 minute. You can even specify a <em>BackoffCoefficient</em> to do exponential backoff which slows down the retries after each execution.</p><p>There were several occasions where the GitHub API returned a gateway time-out (504), and by having this retry policy in place, the activity function execution always succeeded eventually.</p><h2 id=\"2-monitoring-and-alerts\"><a href=\"#2-monitoring-and-alerts\" class=\"heading-anchor\">2. Monitoring &amp; Alerts</a></h2><p>In case something goes wrong with the function app which is not recoverable with retries I want to receive a notification so I can look into the issue. The function app only delivers value when it’s posting updates to Twitter. Therefore I’ve configured <a href=\"https://docs.microsoft.com/en-us/azure/azure-monitor/platform/alerts-overview\" rel=\"noopener\">an alert</a> which sends me an email when the <em>PostUpdate</em> activity function fails 3 times within one hour (equal to the maximum amount of retries). The image below shows the alert configuration:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/37.3.alert-configuration-440w.webp 440w, /assets/images/37.3.alert-configuration-650w.webp 650w, /assets/images/37.3.alert-configuration-960w.webp 960w, /assets/images/37.3.alert-configuration-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/37.3.alert-configuration-1200w.webp\" width=\"1200\" height=\"683\" alt=\"Alert configuration\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Notice that the top right shows a chart with the selected signal events in a time window. In this case, it shows 6 occasions of <em>PostUpdate</em> activity failures within one week. I found this graph quite helpful to look for meaningful signals.</p><p>When <a href=\"https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview\" rel=\"noopener\">Application Insights</a> is enabled for the function app, there are many options for signal selection when configuring the condition of the alert. As can be seen in the image below, the success rate, failure rate, and min/max/avg durations of the functions can be chosen as the signal source:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/37.4.configure-signal-logic-440w.webp 440w, /assets/images/37.4.configure-signal-logic-650w.webp 650w, /assets/images/37.4.configure-signal-logic-832w.webp 832w\" sizes=\"90vw\"><img src=\"/assets/images/37.4.configure-signal-logic-832w.webp\" width=\"832\" height=\"898\" alt=\"Signal selection\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"3-performance\"><a href=\"#3-performance\" class=\"heading-anchor\">3. Performance</a></h2><p>This function app is a very low volume application. It is triggered once every hour, which results in about 51k function executions per month. Even though a high volume or performance is not of my concern in this situation, it is interesting to see how the function app and its dependencies perform. The <a href=\"https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-map\" rel=\"noopener\">Application Map</a> below shows the connections from the function app in the center to its dependent resources. The data shown is aggregated for 24 hours:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/37.5.application-map-440w.webp 440w, /assets/images/37.5.application-map-650w.webp 650w, /assets/images/37.5.application-map-960w.webp 960w, /assets/images/37.5.application-map-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/37.5.application-map-1200w.webp\" width=\"1200\" height=\"913\" alt=\"Application map\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The first time I looked at the Application Map it struck me that there were only 22 instances of the function app. I expected 24 instances since the function app runs every hour. However, it appears that instances are sometimes reused. In this case, two instances had been reused, which explains the 22 individual instances over 24 hours.</p><p>The calls to the <a href=\"azurecomcdn.azureedge.net/en-us/updates/feed/\">Azure Service Updates RSS feed</a> take the longest, 1 sec on average. The calls to Azure Table storage and Queue storage are the fastest, 148.6ms and 109.7ms on average.</p><p>Let’s look deeper into the performance of the GitHub and Twitter HTTP APIs. Here’s a chart that shows the average duration of the HTTP calls in August:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/37.6.dependency-reponse-time-440w.webp 440w, /assets/images/37.6.dependency-reponse-time-650w.webp 650w, /assets/images/37.6.dependency-reponse-time-960w.webp 960w, /assets/images/37.6.dependency-reponse-time-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/37.6.dependency-reponse-time-1200w.webp\" width=\"1200\" height=\"1122\" alt=\"Average duration HTTP APIs\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I recommend looking at these dependency response time charts because they can reveal trends about the performance of the dependencies over time. In this case, there is an apparent increase in duration around 11 to 13 Aug (GitHub was slower in its response then) and after that, the average duration stabilizes around 0.8s.</p><p>Looking at the duration distribution of all calls, we see a large spread ranging from 120ms to 2.9s:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/37.7.durationdistribution-httpapis-440w.webp 440w, /assets/images/37.7.durationdistribution-httpapis-650w.webp 650w, /assets/images/37.7.durationdistribution-httpapis-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/37.7.durationdistribution-httpapis-960w.webp\" width=\"960\" height=\"546\" alt=\"Duration distribution for HTTP calls to GitHub and Twitter\" loading=\"lazy\" decoding=\"async\"></picture></p><p>There’s nothing I can do about the performance of the external dependencies, but it is important to realize that calls to other systems are never constant. The network is not 100% reliable and latency is never 0. I recommend reading about the <a href=\"http://www.rgoarchitects.com/Files/fallacies.pdf\" rel=\"noopener\"><em>fallacies of distributed computing</em></a>.</p><p>Something which is under my control are the functions I wrote myself. Here’s the distribution chart for the ReleaseUpdateOrchestration function:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/37.8.durationdistribution-orchestration-440w.webp 440w, /assets/images/37.8.durationdistribution-orchestration-650w.webp 650w, /assets/images/37.8.durationdistribution-orchestration-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/37.8.durationdistribution-orchestration-960w.webp\" width=\"960\" height=\"546\" alt=\"Duration distribution for ReleaseUpdateOrchestration\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Again there is a wide distribution of durations. Part of this variance is due to performance differences across function app instances, but another factor is that orchestrator functions are replayed several times and each time they proceed further in the orchestrator code and take longer to complete. I plan to do a more thorough investigation of how significant this effect is.</p><h2 id=\"4-costs\"><a href=\"#4-costs\" class=\"heading-anchor\">4. Costs</a></h2><p>I wanted a cheap solution for this bot. I’m running this application as a personal side project and no-one else is paying for it. I made a rough calculation up front and realized that the cost would be minimal since the first 1 million executions for the Azure Functions consumption plan <a href=\"https://azure.microsoft.com/en-us/pricing/details/functions/\" rel=\"noopener\">are free</a>. In addition, I’m using a tiny set of data (repository, release, and announcement information) which is stored in Azure Table Storage, which is <a href=\"https://azure.microsoft.com/en-us/pricing/details/storage/tables/\" rel=\"noopener\">very cheap to use</a>.</p><h3 id=\"cost-breakdown\"><a href=\"#cost-breakdown\" class=\"heading-anchor\">Cost breakdown</a></h3><p>When we look at the accumulated costs of the resource group since its inception, it’s clear the costs are extremely low, only 1.22 Euro from Feb-Aug 2019.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/37.9.cost-analysis-440w.webp 440w, /assets/images/37.9.cost-analysis-650w.webp 650w, /assets/images/37.9.cost-analysis-960w.webp 960w, /assets/images/37.9.cost-analysis-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/37.9.cost-analysis-1200w.webp\" width=\"1200\" height=\"681\" alt=\"Cost analysis\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The bright-green part of the chart is the actual costs until the 1st of September. The lighter green is the forecast costs. I think the forecast is a bit on the high side, there aren’t any new GitHub repositories to monitor (although I’m looking to include other sources) and the number of releases is unlikely to change drastically. Nevertheless, it’s a nice feature to have this forecast.</p><p>The three donut charts below are different cost breakdowns. The one on the left is per <em>Meter category</em> which is comparable with the different service types. The service which is most costly (1.21 EUR) is the storage account.</p><p>The middle donut chart breaks the cost down per <em>Meter subcategory</em>; this is already more insightful because it shows that storage queues are the highest cost. It is important to realize that Durable Functions relies heavily on queues to schedule the orchestrator and activity functions. So this cost is explainable but might be unexpected if one is not aware of the inner workings of Durable Functions.</p><p>Finally, on the right, the cost is broken down per <em>Meter</em>. Some of this information is clear such as <em>lrs data stored</em> (LRS=locally redundant storage), but others are a bit more cryptic, like <em>class 1</em> and <em>class 2</em> operations. Microsoft made this classification to group certain operations together which have equal cost. <em>Class 1</em> is related to create, put, update, and delete operations on queue messages. <em>Class 2</em> is related to get and peek operations on messages.</p><h2 id=\"learnings\"><a href=\"#learnings\" class=\"heading-anchor\">Learnings</a></h2><p>I had the opportunity to attend the <em>Advanced Distributed Systems Design course</em> by <a href=\"https://twitter.com/udidahan\" rel=\"noopener\">Udi Dahan</a> earlier this year and that was really an eye-opener. It really helped me to understand the fallacies of distributed computing better and how to act on them. The Twitterbot I made is a very small application and has little impact in case it does not work, but it proved to be a nice playground to put some learnings into practice.</p><ol class=\"list\"><li><p>Failures are inevitable, so you better be prepared to have the system cope with it where possible and have a good process &amp; tooling in place to respond to issues. In my case, I had build &amp; release pipelines configured in Azure DevOps from the start, which enabled automated unit testing and pushing fixes to production quickly.</p></li><li><p>You need monitoring and alerts setup when you’re running in production; otherwise, you’re blind to anything that happens. Try to identify a small set of most important metrics and only put alerts on those.</p></li><li><p>To really understand how your solution is performing you need to see how it’s running in production. Enable Application Insights and spend a good amount of time navigating through the various blades in the Azure Portal. The Application Map, Failures and Performance blades are easy to get started with and provide a wealth of information.</p></li><li><p>The Azure Functions Consumption plan combined with table/blob storage is cheap for low to medium volume workloads. Make an effort in calculating the function executions up-front, so you have an educated guess if a Consumption plan or App Service plan is best for your situation. Note that by using Durable Functions, you’ll pay a bit more due to queue and table storage usage and more function executions. However, don’t let this hold you back. The Durable Functions framework provides many great features out of the box, which you don’t need to write and maintain yourself. So I’m pretty confident the benefits outweigh the costs.</p></li></ol><h2 id=\"open-source\"><a href=\"#open-source\" class=\"heading-anchor\">Open source</a></h2><p>The code for the Twitterbot is on <a href=\"https://github.com/marcduiker/az-func-updates\" rel=\"noopener\">GitHub</a> so if you want to look at the details feel free to clone or fork it. I have plans to extend the bot with <a href=\"https://github.com/marcduiker/az-func-updates/issues\" rel=\"noopener\">more features</a> if you have additional ideas, feel free to add those as issues or submit a PR 😃.</p>",
      "date_published": "2019-09-05T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/durable-functions-api-purge-terminate/",
      "url": "https://marcduiker.dev/articles/durable-functions-api-purge-terminate/",
      "title": "Discovering the Durable Functions API - Purge &amp; Terminate Orchestrations (DurableOrchestrationClient part 3)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/36.purge-terminate-cover-440w.webp 440w, /assets/images/36.purge-terminate-cover-650w.webp 650w, /assets/images/36.purge-terminate-cover-900w.webp 900w\" sizes=\"90vw\"><img src=\"/assets/images/36.purge-terminate-cover-900w.webp\" width=\"900\" height=\"381\" alt=\"Methods in DurableOrchestrationClientBase\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"durableorchestrationclientbase-class-purge-and-terminate\"><a href=\"#durableorchestrationclientbase-class-purge-and-terminate\" class=\"heading-anchor\">DurableOrchestrationClient(Base) class - Purge &amp; Terminate</a></h2><p>This post is the third part of a series of blogs/vlogs to discover the Durable Functions API.</p><p>In the video linked below, I’m looking into the functionality from the <a href=\"https://github.com/Azure/azure-functions-durable-extension/blob/master/src/WebJobs.Extensions.DurableTask/DurableOrchestrationClientBase.cs\" rel=\"noopener\"><code>DurableOrchestrationClient</code>(<code>Base</code>)</a> class which is used to:</p><ul class=\"list\"><li>Purge the history of orchestration instances</li><li>Terminate a running orchestration</li></ul><h3 id=\"purging\"><a href=\"#purging\" class=\"heading-anchor\">Purging</a></h3><p>Purging the history of orchestration instances means deleting the records from table storage. I recommend doing this regularly, for instance using timer triggered function, so you won’t clutter up your storage account with gigabytes of data you likely don’t need. I usually remove all completed instances older than a week but keep the failed, canceled and terminated ones a bit longer. Make sure you don’t inadvertently purge the running and pending instances!</p><p>There are two methods to purge the history of orchestration instances. The first method listed below deletes the history for multiple orchestration instances. The method requires a DateTime range and a collection of <code>OrchestrationStatus</code> enum values. These arguments act as a filter, so only the instance history within the DateTime range and selected statuses are purged:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span>PurgeHistoryResult<span class=\"token punctuation\">&gt;</span></span> <span class=\"token function\">PurgeInstanceHistoryAsync</span><span class=\"token punctuation\">(</span>\n    <span class=\"token class-name\">DateTime</span> createdTimeFrom<span class=\"token punctuation\">,</span> \n    <span class=\"token class-name\">DateTime<span class=\"token punctuation\">?</span></span> createdTimeTo<span class=\"token punctuation\">,</span> \n    <span class=\"token class-name\">IEnumerable<span class=\"token punctuation\">&lt;</span>OrchestrationStatus<span class=\"token punctuation\">&gt;</span></span> runtimeStatus<span class=\"token punctuation\">)</span></code></pre><p>The second method deletes the history of a single orchestration instance:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span>PurgeHistoryResult<span class=\"token punctuation\">&gt;</span></span> <span class=\"token function\">PurgeInstanceHistoryAsync</span><span class=\"token punctuation\">(</span>\n        <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> instanceId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><h3 id=\"termination\"><a href=\"#termination\" class=\"heading-anchor\">Termination</a></h3><p>Terminating an orchestration instance means you stop a running orchestration. You only change the state of the instance. The instance history is still available in table storage. Using this method should only be used in exceptional cases. Perhaps you have an orchestration with a bug, so it keeps running forever, and you want it to stop.</p><p>The method requires an orchestration instance ID and a reason why you are stopping the instance:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token return-type class-name\">Task</span> <span class=\"token function\">TerminateAsync</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">string</span></span> instanceId<span class=\"token punctuation\">,</span> <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> reason<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><h3 id=\"video\"><a href=\"#video\" class=\"heading-anchor\">Video</a></h3><p>Here’s the video, please give it a thumbs up if you like it and subscribe to my channel if you haven’t done so already:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/ePPEcNOzlnk\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><h3 id=\"resources\"><a href=\"#resources\" class=\"heading-anchor\">Resources</a></h3><ul class=\"list\"><li>GitHub repo: <a href=\"https://github.com/marcduiker/demos-azure-durable-functions\" rel=\"noopener\">github.com/marcduiker/demos-azure-durable-functions</a>.</li></ul><h3 id=\"links-to-other-posts-in-this-series\"><a href=\"#links-to-other-posts-in-this-series\" class=\"heading-anchor\">Links to other posts in this series</a></h3><ul class=\"list\"><li><a href=\"/articles/durable-functions-api-durableorchestrationclient-1\">Starting Orchestrations (DurableOrchestrationClient Part 1)</a></li><li><a href=\"/articles/durable-functions-api-durableorchestrationclient-2\">Retrieving the Orchestration Status (DurableOrchestrationClient Part 2)</a></li><li>Purge &amp; Terminate Orchestrations (DurableOrchestrationClient Part 3)</li></ul>",
      "date_published": "2019-08-12T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/creating-azure-functions-updates-twitterbot/",
      "url": "https://marcduiker.dev/articles/creating-azure-functions-updates-twitterbot/",
      "title": "Creating the Azure Functions Updates Twitterbot",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/35.1.azfuncupdates_twitter-440w.webp 440w, /assets/images/35.1.azfuncupdates_twitter-650w.webp 650w, /assets/images/35.1.azfuncupdates_twitter-960w.webp 960w, /assets/images/35.1.azfuncupdates_twitter-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/35.1.azfuncupdates_twitter-1200w.webp\" width=\"1200\" height=\"699\" alt=\"Azure Functions Updates component diagram\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"tl;dr\"><a href=\"#tl;dr\" class=\"heading-anchor\">TL;DR</a></h2><p>Go to <a href=\"https://twitter.com/az_func_updates\" rel=\"noopener\">https://twitter.com/az_func_updates</a> and follow that account to stay up to date with new Azure Functions releases!</p><h2 id=\"what-im-trying-to-solve\"><a href=\"#what-im-trying-to-solve\" class=\"heading-anchor\">What I’m trying to solve</a></h2><p>The <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/\" rel=\"noopener\">Azure Functions</a> ecosystem consists of a lot of moving parts. There’s the runtime, core tools, VS templates, Webjobs SDK and its dozen or so extensions, definitions for writing functions using TypeScript and much more.</p><p>As a developer, it’s important you are aware of the latest releases and what is compatible with one another. I’ve spent quite some time troubleshooting incompatible runtimes and packages in the past, and it would be great if this experience can be improved.</p><h2 id=\"the-idea\"><a href=\"#the-idea\" class=\"heading-anchor\">The idea</a></h2><p>I was thinking about a way to notify developers about new releases related to Azure Functions. Almost all of the Azure Functions components are on GitHub so I can use the <a href=\"https://developer.github.com/\" rel=\"noopener\">GitHub API</a> to check for latest releases.</p><p>Since the IT community is very active on Twitter that can be the communication channel, so the idea of an <a href=\"https://twitter.com/az_func_updates\" rel=\"noopener\">Azure Functions Updates Twitterbot</a> was born.</p><p>Surely this does not solve the issue of dealing with incompatible components, but it does help a bit in spreading the information about new releases and knowing when to update which exact piece of the puzzle.</p><h2 id=\"the-implementation\"><a href=\"#the-implementation\" class=\"heading-anchor\">The implementation</a></h2><p>Since I’m a big fan of Azure Functions myself, I wanted to use this service as the backend for the Twitterbot. Logic Apps could also have been a valid option for a part of it, but I prefer to have local debugging and testing which is easily done with Azure Functions.</p><p>The design of the application is as follows:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/35.2.azfunctionupdates_diagram-440w.webp 440w, /assets/images/35.2.azfunctionupdates_diagram-650w.webp 650w, /assets/images/35.2.azfunctionupdates_diagram-960w.webp 960w, /assets/images/35.2.azfunctionupdates_diagram-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/35.2.azfunctionupdates_diagram-1200w.webp\" width=\"1200\" height=\"395\" alt=\"Azure Functions Updates component diagram\" loading=\"lazy\" decoding=\"async\"></picture></p><h3 id=\"storage-azure-table-storage\"><a href=\"#storage-azure-table-storage\" class=\"heading-anchor\">Storage: Azure Table Storage</a></h3><p>The application needs to store two things:</p><ol class=\"list\"><li>The GitHub repositories to check for new releases.</li><li>The latest release for each of the repositories to compare against.</li></ol><p>Both require very little storage. I decided on using Azure Table Storage since that is easy to setup &amp; use and it is also very performant.</p><p>The repositories to check are stored in the <code>RepositoryConfigurations</code> table:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/35.3.repository_configurations-440w.webp 440w, /assets/images/35.3.repository_configurations-650w.webp 650w, /assets/images/35.3.repository_configurations-960w.webp 960w, /assets/images/35.3.repository_configurations-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/35.3.repository_configurations-1200w.webp\" width=\"1200\" height=\"249\" alt=\"Azure Storage Table with repository configurations\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The release information retrieved from GitHub is stored in the <code>Releases</code> table:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/35.4.releases-440w.webp 440w, /assets/images/35.4.releases-650w.webp 650w, /assets/images/35.4.releases-960w.webp 960w, /assets/images/35.4.releases-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/35.4.releases-1200w.webp\" width=\"1200\" height=\"250\" alt=\"Azure Storage Table with release info from GitHub\" loading=\"lazy\" decoding=\"async\"></picture></p><p>I’m not storing the entire GitHub Release object, only the properties I need (or plan to use soon).</p><h3 id=\"compute-azure-functions\"><a href=\"#compute-azure-functions\" class=\"heading-anchor\">Compute: Azure Functions</a></h3><p>As mentioned before I’m using Azure Functions for the compute part of this application. In specific I’m using the <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview\" rel=\"noopener\">Durable Functions</a> extension to control the flow of the application. A timer trigger starts the <a href=\"https://github.com/marcduiker/az-func-updates/blob/master/src/AzureFunctionsUpdates/Orchestrations/ReleaseUpdateOrchestration.cs\" rel=\"noopener\"><code>ReleaseUpdateOrchestration</code></a> function each hour.</p><p>The flow (in pseudo code) is as follows:</p><pre class=\"language-plaintext\"><code class=\"language-plaintext\">- GetRepositoryConfigurations\n- for each of the repositories:\n    - GetLatestReleaseFromGitHub\n    - GetLatestReleaseFromHistory\n- for each of the repositories:\n    - if release from GitHub is not equal to release from History\n        - SaveLatestRelease\n        - PostUpdate</code></pre><p>The orchestrator function uses the fan-out/fan-in pattern since it can call the same activity functions for each of the configured repositories in parallel.</p><h3 id=\"github-api\"><a href=\"#github-api\" class=\"heading-anchor\">GitHub API</a></h3><p>For the GitHub integration, I’m using <a href=\"https://github.com/octokit/octokit.net\" rel=\"noopener\">Octokit</a>, an excellent GitHub API client library for .NET.</p><p>The usage is the following:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> client <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">GitHubClient</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">ProductHeaderValue</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"SomeApplicationIdentifier\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> latestRelease <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> client<span class=\"token punctuation\">.</span>Repository<span class=\"token punctuation\">.</span>Release<span class=\"token punctuation\">.</span><span class=\"token function\">GetLatest</span><span class=\"token punctuation\">(</span>\n    repoConfiguration<span class=\"token punctuation\">.</span>RepositoryOwner<span class=\"token punctuation\">,</span>\n    repoConfiguration<span class=\"token punctuation\">.</span>RepositoryName<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>I’m doing unauthenticated requests which means the application is <a href=\"https://developer.github.com/v3/#rate-limiting\" rel=\"noopener\">rate limited at 60 requests per hour</a>. I’m far from that number of repositories to check, but it’s good to be aware this doesn’t scale very well. For authenticated requests, the rate limiting is set much higher, at 5000 requests per hour.</p><h3 id=\"twitter-api\"><a href=\"#twitter-api\" class=\"heading-anchor\">Twitter API</a></h3><p>To have an application post tweets, you need a Twitter developer account and set up a Twitter application. There is quite some documentation about this (almost too much), but it’s quite easy to follow when you go through the <a href=\"https://developer.twitter.com/en/docs/basics/getting-started\" rel=\"noopener\">getting started docs</a>. You basically need to fill in a couple of online forms and promise you won’t do any evil with you Twitterbot.</p><p>To post the updates to Twitter from my function app I’m using <a href=\"https://github.com/linvi/tweetinvi\" rel=\"noopener\">Tweetinvi</a>. I found this Twitter client library for .NET Core very easy to use:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> creds <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">TwitterCredentials</span><span class=\"token punctuation\">(</span>\n     consumerApiKey<span class=\"token punctuation\">,</span> \n     consumerApiSecret<span class=\"token punctuation\">,</span> \n     accessToken<span class=\"token punctuation\">,</span> \n     accessTokenSecret<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> tweetMessage <span class=\"token operator\">=</span> <span class=\"token function\">CreateMessage</span><span class=\"token punctuation\">(</span>newRelease<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> tweet <span class=\"token operator\">=</span> Auth<span class=\"token punctuation\">.</span><span class=\"token function\">ExecuteOperationWithCredentials</span><span class=\"token punctuation\">(</span>creds<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> Tweet<span class=\"token punctuation\">.</span><span class=\"token function\">PublishTweet</span><span class=\"token punctuation\">(</span>tweetMessage<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><h2 id=\"overall-development-experience\"><a href=\"#overall-development-experience\" class=\"heading-anchor\">Overall development experience</a></h2><p>I built this application in about 6 evenings over the last month. I find it quite amazing that thanks to the work of others, who have built these cloud services and client libraries, I can make something like this in such little time.</p><p>Development was mostly done in Visual Studio 2019 Preview and a bit of VS Code. I’ve set up a CI/CD pipeline in Azure DevOps so as soon as I merge to master a new release is built and deployed to Azure.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/35.5.azuredevops-440w.webp 440w, /assets/images/35.5.azuredevops-650w.webp 650w, /assets/images/35.5.azuredevops-960w.webp 960w, /assets/images/35.5.azuredevops-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/35.5.azuredevops-1200w.webp\" width=\"1200\" height=\"733\" alt=\"Azure Devops CD pipeline\" loading=\"lazy\" decoding=\"async\"></picture></p><p>It was certainly an enjoyable experience to make this, and I hope you will benefit from it.</p><p>I already have plans to extend the application beyond GitHub release updates. So in time, there will be some significant upgrades.</p><h2 id=\"resources-and-feedback\"><a href=\"#resources-and-feedback\" class=\"heading-anchor\">Resources &amp; feedback</a></h2><p>The full source code of the Azure Functions Updates Twitterbot is found in the <a href=\"https://github.com/marcduiker/az-func-updates\" rel=\"noopener\">az-func-updates</a> repo on GitHub.</p><p>If you have feature requests or suggestions, feel free to add those as an <a href=\"https://github.com/marcduiker/az-func-updates/issues\" rel=\"noopener\">issue</a>, so we can discuss it.</p>",
      "date_published": "2019-03-03T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/durable-functions-api-durableorchestrationclient-2/",
      "url": "https://marcduiker.dev/articles/durable-functions-api-durableorchestrationclient-2/",
      "title": "Discovering the Durable Functions API - Retrieving the orchestration status (DurableOrchestrationClient part 2)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/34.durable-orchestration-clientbase2_900-440w.webp 440w, /assets/images/34.durable-orchestration-clientbase2_900-650w.webp 650w, /assets/images/34.durable-orchestration-clientbase2_900-900w.webp 900w\" sizes=\"90vw\"><img src=\"/assets/images/34.durable-orchestration-clientbase2_900-900w.webp\" width=\"900\" height=\"380\" alt=\"Durable Orchestration ClientBase\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"durableorchestrationclientbase-class-retrieving-status\"><a href=\"#durableorchestrationclientbase-class-retrieving-status\" class=\"heading-anchor\">DurableOrchestrationClient(Base) class - Retrieving status</a></h2><p>This post is the second part of a series of blogs/vlogs to discover the Durable Functions API.</p><p>In the video linked below, I’m looking into the functionality from the <a href=\"https://github.com/Azure/azure-functions-durable-extension/blob/master/src/WebJobs.Extensions.DurableTask/DurableOrchestrationClientBase.cs\" rel=\"noopener\"><code>DurableOrchestrationClient</code>(<code>Base</code>)</a> class, which can be used to retrieve the status of orchestration instances.</p><p>These are the methods to retrieve the status of <strong>one</strong> orchestration and return a <a href=\"https://github.com/Azure/azure-functions-durable-extension/blob/master/src/WebJobs.Extensions.DurableTask/DurableOrchestrationStatus.cs\" rel=\"noopener\"><code>DurableOrchestrationStatus</code></a> object:</p><ul class=\"list\"><li><code>GetStatusAsync(string instanceId)</code></li><li><code>GetStatusAsync(string instanceId, bool showHistory);</code></li><li><code>GetStatusAsync(string instanceId, bool showHistory, bool showHistoryOutput, bool showInput = true);</code></li></ul><p>These are the methods to retrieve the status for <strong>multiple</strong> orchestrations and they return an <code>IList&lt;DurableOrchestrationStatus&gt;</code>:</p><ul class=\"list\"><li><code>GetStatusAsync(CancellationToken cancellationToken = default(CancellationToken));</code></li><li><code>GetStatusAsync(DateTime createdTimeFrom, DateTime? createdTimeTo, IEnumerable&lt;OrchestrationRuntimeStatus&gt; runtimeStatus, CancellationToken cancellationToken = default(CancellationToken));</code></li></ul><p>Here’s the video, please give it a thumbs up if you like it and subscribe to my channel if you haven’t done so already:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/d5fsidj_EDs\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><h3 id=\"resources\"><a href=\"#resources\" class=\"heading-anchor\">Resources</a></h3><ul class=\"list\"><li>GitHub repo: <a href=\"https://github.com/marcduiker/demos-azure-durable-functions\" rel=\"noopener\">github.com/marcduiker/demos-azure-durable-functions</a>.</li><li>Microsoft docs: <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-http-api\" rel=\"noopener\">Durable Functions HTTP API</a>.</li></ul><h3 id=\"links-to-other-posts-in-this-series\"><a href=\"#links-to-other-posts-in-this-series\" class=\"heading-anchor\">Links to other posts in this series</a></h3><ul class=\"list\"><li><a href=\"/articles/durable-functions-api-durableorchestrationclient-1\">Starting Orchestrations (DurableOrchestrationClient Part 1)</a></li><li>Retrieving the Orchestration Status (DurableOrchestrationClient Part 2)</li><li><a href=\"/articles/durable-functions-api-purge-terminate\">Purge &amp; Terminate Orchestrations (DurableOrchestrationClient Part 3)</a></li></ul>",
      "date_published": "2019-02-10T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/durable-functions-api-durableorchestrationclient-1/",
      "url": "https://marcduiker.dev/articles/durable-functions-api-durableorchestrationclient-1/",
      "title": "Discovering the Durable Functions API - Starting orchestrations (DurableOrchestrationClient part 1)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/33.durable-orchestration-clientbase1_900-440w.webp 440w, /assets/images/33.durable-orchestration-clientbase1_900-650w.webp 650w, /assets/images/33.durable-orchestration-clientbase1_900-900w.webp 900w\" sizes=\"90vw\"><img src=\"/assets/images/33.durable-orchestration-clientbase1_900-900w.webp\" width=\"900\" height=\"380\" alt=\"Durable Orchestration ClientBase\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"durableorchestrationclientbase-class-starting-and-waiting-for-completion\"><a href=\"#durableorchestrationclientbase-class-starting-and-waiting-for-completion\" class=\"heading-anchor\">DurableOrchestrationClient(Base) class - Starting &amp; waiting for completion</a></h2><p>This post is the first part of a series of blogs/vlogs to discover the Durable Functions API.</p><p>In the video linked below, I’m looking into methods from the <a href=\"https://github.com/Azure/azure-functions-durable-extension/blob/master/src/WebJobs.Extensions.DurableTask/DurableOrchestrationClientBase.cs\" rel=\"noopener\"><code>DurableOrchestrationClient</code>(<code>Base</code>)</a> class on how to start a new orchestration instance and how to retrieve the status and the result of the instance:</p><ul class=\"list\"><li><code>StartNewAsync(string orchestratorFunctionName, object input)</code></li><li><code>StartNewAsync(string orchestratorFunctionName, string instanceId, object input)</code></li><li><code>CreateCheckStatusResponse(HttpRequestMessage request, string instanceId)</code></li><li><code>WaitForCompletionOrCreateCheckStatusResponseAsync(HttpRequestMessage request, string instanceId)</code></li><li><code>WaitForCompletionOrCreateCheckStatusResponseAsync(HttpRequestMessage request, string instanceId, TimeSpan timeout)</code></li><li><code>WaitForCompletionOrCreateCheckStatusResponseAsync(HttpRequestMessage request, string instanceId, TimeSpan timeout, TimeSpan retryInterval)</code></li></ul><p>Here’s the video, please give it a thumbs up if you like it and subscribe to my channel so you’ll be notified of new videos:</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/mRDesdK3W8Q\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><h3 id=\"resources\"><a href=\"#resources\" class=\"heading-anchor\">Resources</a></h3><ul class=\"list\"><li>GitHub repo: <a href=\"https://github.com/marcduiker/demos-azure-durable-functions\" rel=\"noopener\">github.com/marcduiker/demos-azure-durable-functions</a>.</li><li>Microsoft docs: <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-http-api\" rel=\"noopener\">Durable Functions HTTP API</a>.</li><li>Microsoft docs: <a href=\"https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#optional-uri-parameters-and-default-values\" rel=\"noopener\">Optional uri parameters and default values</a>.</li></ul><h3 id=\"links-to-other-posts-in-this-series\"><a href=\"#links-to-other-posts-in-this-series\" class=\"heading-anchor\">Links to other posts in this series</a></h3><ul class=\"list\"><li>Starting orchestrations (DurableOrchestrationClient Part 1)</li><li><a href=\"/articles/durable-functions-api-durableorchestrationclient-2\">Retrieving the orchestration status (DurableOrchestrationClient Part 2)</a></li><li><a href=\"/articles/durable-functions-api-purge-terminate\">Purge &amp; Terminate Orchestrations (DurableOrchestrationClient Part 3)</a></li></ul>",
      "date_published": "2019-01-07T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/the-anatomy-of-function-naming/",
      "url": "https://marcduiker.dev/articles/the-anatomy-of-function-naming/",
      "title": "Durable Functions - The Anatomy of Function Naming",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/32.function-naming-440w.webp 440w, /assets/images/32.function-naming-650w.webp 650w, /assets/images/32.function-naming-859w.webp 859w\" sizes=\"90vw\"><img src=\"/assets/images/32.function-naming-859w.webp\" width=\"859\" height=\"306\" alt=\"Anatomy of Functions Naming\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"function-names-in-the-wild\"><a href=\"#function-names-in-the-wild\" class=\"heading-anchor\">Function Names in the Wild</a></h2><p>Whenever I see examples or implementations of Azure Functions I always see this:</p><p><code>[FunctionName(\"myfunction\")]</code></p><p>Where the function name is present as a direct string literal. I’m not questioning that it <em>is</em> a string because that’s simply the way the <code>FunctionName</code> attribute works currently. I’m more concerned how the string got there.</p><p>I’d like to show two ways to refer to function names in a safe and consistent way. This is especially useful when you’re using <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/durable-functions-overview\" rel=\"noopener\">Durable Functions</a> and need to refer to activity function names in your orchestration function.</p><h2 id=\"literal-strings-and-meaning\"><a href=\"#literal-strings-and-meaning\" class=\"heading-anchor\">Literal Strings and Meaning</a></h2><p>I admit that I’ve probably been reading too much <em>Code Complete</em> and <em>Clean Code</em> in my life because whenever I see literal strings in code I usually get the chills. So I’m probably overreacting a bit…</p><p>What I find most annoying about the direct usage of literal strings in general is that the intent or meaning is usually unclear or ambiguous (aka <a href=\"https://en.wikipedia.org/wiki/Magic_string\" rel=\"noopener\">magic string</a>).</p><p>Although the meaning of the string in the <code>[FunctionName]</code> attribute is clear, what valid input is for the name is not clear. You need to dig into the <a href=\"https://github.com/Azure/azure-webjobs-sdk/blob/9f96d3f1e63ae1241431990f256f1b2e6880167f/src/Microsoft.Azure.WebJobs/FunctionNameAttribute.cs#L34\" rel=\"noopener\">FunctionNameAttribute.cs</a> class to find the regex that validates the <code>[FunctionName]</code> attribute string:</p><p><code>^[a-z][a-z0-9_\\-]{0,127}$(?&lt;!^host$)</code></p><p>… and then you need to understand the regex as well 😉.</p><p>In addition, you will only be notified of an invalid <code>[FunctionName]</code> attribute during runtime:</p><p><code>\"Orchestrator function 'HelloName' failed:</code><br><code>The function 'Hello.Activity' doesn't exist, is disabled, or is not an activity function.</code><br><code>The following are the active activity functions: '...'\"</code></p><p>If we could use a type-safe way of naming functions, invalid names could be detected much earlier.</p><h2 id=\"naming-is-hard\"><a href=\"#naming-is-hard\" class=\"heading-anchor\">Naming is Hard</a></h2><p>I definitely agree with <a href=\"https://skeptics.stackexchange.com/questions/19836/has-phil-karlton-ever-said-there-are-only-two-hard-things-in-computer-science\" rel=\"noopener\">Phil Karlton</a> on that one. It often occurs that I rename a class or method just minutes after I created it.</p><p>When it comes to naming functions I always start with a verb followed by a noun related to the domain language. I don’t mind verbose class or method names as long as they clearly communicate their intent.</p><p>For example, I recently updated my Durable Functions demo code where I wrap some calls to <a href=\"http://swapi.co\" rel=\"noopener\">swapi.co</a> (Star Wars API) in activity functions. Those function names are named <code>GetCharacter</code>, <code>SearchCharacter</code>, <code>GetPlanet</code> and <code>SearchPlanet</code>. I hope you will agree the intent is clear.</p><h2 id=\"structuring-orchestration-and-activity-functions\"><a href=\"#structuring-orchestration-and-activity-functions\" class=\"heading-anchor\">Structuring Orchestration and Activity Functions</a></h2><p>I prefer my orchestration and activity functions to be in separate classes. In addition, I prefer to have each activity function in its own class (and file). For me this brings the following benefits:</p><ul class=\"list\"><li>Files and classes are kept very small and therefore very readable.</li><li>When I add a new activity function I don’t risk ‘touching’ other functions (except to call it from the orchestration function of course).</li><li>It’s very easy to locate the individual functions in Visual Studio.</li><li>I can use the class name as the <code>FunctionName</code> identifier! (keep reading…)</li></ul><h2 id=\"safe-and-consistent-naming\"><a href=\"#safe-and-consistent-naming\" class=\"heading-anchor\">Safe and Consistent Naming</a></h2><p>An orchestration function depends on the implemented activity functions and both types of functions are located in the same Function App. This means we don’t need to provide string literals separately for the <code>[FunctionName]</code> in the activity and in the <code>CallActivityAsync()</code> methods in the orchestration but we can do something better.</p><h3 id=\"option-1-static-class-with-constants\"><a href=\"#option-1-static-class-with-constants\" class=\"heading-anchor\">Option 1: Static Class with Constants</a></h3><p>The most basic option to have consistent naming of functions is to use a static class with string constants. Now both orchestration and activity functions can use that naming class:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DurableFunctions<span class=\"token punctuation\">.</span>Demo<span class=\"token punctuation\">.</span>DotNetCore<span class=\"token punctuation\">.</span>Basics</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">FunctionName</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">public</span> <span class=\"token keyword\">const</span> <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> HelloNameActivity <span class=\"token operator\">=</span> <span class=\"token string\">\"HelloNameActivity\"</span><span class=\"token punctuation\">;</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token keyword\">const</span> <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> HelloNameOrchestration <span class=\"token operator\">=</span> <span class=\"token string\">\"HelloNameOrchestration\"</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DurableFunctions<span class=\"token punctuation\">.</span>Demo<span class=\"token punctuation\">.</span>DotNetCore<span class=\"token punctuation\">.</span>Basics<span class=\"token punctuation\">.</span>Activities</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">HelloNameActivity</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">FunctionName</span><span class=\"token attribute-arguments\"><span class=\"token punctuation\">(</span>FunctionName<span class=\"token punctuation\">.</span>HelloNameActivity<span class=\"token punctuation\">)</span></span></span><span class=\"token punctuation\">]</span>\n        <span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> <span class=\"token function\">Run</span><span class=\"token punctuation\">(</span>\n            <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">ActivityTrigger</span></span><span class=\"token punctuation\">]</span> <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> name<span class=\"token punctuation\">,</span>\n            <span class=\"token class-name\">ILogger</span> logger<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            logger<span class=\"token punctuation\">.</span><span class=\"token function\">LogInformation</span><span class=\"token punctuation\">(</span><span class=\"token interpolation-string\"><span class=\"token string\">$\"Name: </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">name</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">\"</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token keyword\">return</span> <span class=\"token interpolation-string\"><span class=\"token string\">$\"Hello </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">name</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">!\"</span></span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>I suggest using this option only if you require a function naming convention that is not compatible with your C# class naming convention (e.g. you require hyphens or underscores in the function name).</p><p>You could add comments what valid function names are and, even more important, you should add unit tests that fail when invalid names are used. Then at least you know during your CI/CD process that something is wrong.</p><h3 id=\"option-2-the-power-of-nameof\"><a href=\"#option-2-the-power-of-nameof\" class=\"heading-anchor\">Option 2: The Power of nameof()</a></h3><p>Since I write only one function per class I do the following when specifying the <code>FunctionName</code> attribute value:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DurableFunctions<span class=\"token punctuation\">.</span>Demo<span class=\"token punctuation\">.</span>DotNetCore<span class=\"token punctuation\">.</span>Basics<span class=\"token punctuation\">.</span>Activities</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">HelloNameActivity</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">FunctionName</span><span class=\"token attribute-arguments\"><span class=\"token punctuation\">(</span><span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>HelloNameActivity<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span></span></span><span class=\"token punctuation\">]</span>\n        <span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> <span class=\"token function\">Run</span><span class=\"token punctuation\">(</span>\n            <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">ActivityTrigger</span></span><span class=\"token punctuation\">]</span> <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> name<span class=\"token punctuation\">,</span>\n            <span class=\"token class-name\">ILogger</span> logger<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            logger<span class=\"token punctuation\">.</span><span class=\"token function\">LogInformation</span><span class=\"token punctuation\">(</span><span class=\"token interpolation-string\"><span class=\"token string\">$\"Name: </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">name</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">\"</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token keyword\">return</span> <span class=\"token interpolation-string\"><span class=\"token string\">$\"Hello </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">name</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">!\"</span></span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>The <code>nameof()</code> expression is available since <a href=\"https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/nameof\" rel=\"noopener\">C# 6.0</a> and provides a very clean way to use strings based on class names.</p><p>The benefits are that:</p><ul class=\"list\"><li>I can now refer to an activity function in a strongly typed fashion so renaming/refactoring is less fragile.</li><li>I always use names that are valid function names.</li><li>I don’t have to think about creating <strong>two</strong> names (class and function), just <strong>one</strong>.</li></ul><p>The downsides are that:</p><ul class=\"list\"><li>Function names are limited to valid C# class names.</li><li>There can only be one function method per class.</li></ul><p>The benefits definitely outweigh the downsides for me.</p><h2 id=\"which-one-do-you-prefer\"><a href=\"#which-one-do-you-prefer\" class=\"heading-anchor\">Which one do you prefer?</a></h2><p>So there you go, two options to refer to your function names in a safe and consistent manner when using Durable Functions.</p><p>I’d like to know which one do you prefer (or maybe you have an alternative), so feel free to reach out to me on <a href=\"https://twitter.com/marcduiker\" rel=\"noopener\">Twitter</a> or <a href=\"https://github.com/marcduiker/demos-azure-durable-functions/issues\" rel=\"noopener\">GitHub</a>.</p><p>Related to this subject is this issue on Github: <a href=\"https://github.com/Azure/azure-functions-core-tools/issues/257\" rel=\"noopener\">Convention-based FunctionName &amp; HttpTrigger Routes</a>.</p><h2 id=\"resources\"><a href=\"#resources\" class=\"heading-anchor\">Resources</a></h2><p>The full souce code with various demos about Durable Functions can be found in my <a href=\"https://github.com/marcduiker/demos-azure-durable-functions\" rel=\"noopener\">demos-azure-durable-functions repo on GitHub</a>.</p>",
      "date_published": "2018-06-21T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/durable-functions-youtube-part3/",
      "url": "https://marcduiker.dev/articles/durable-functions-youtube-part3/",
      "title": "Durable Functions on YouTube (part 3) - The Function Chaining Pattern",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/31.durable-functions-youtube3-440w.webp 440w, /assets/images/31.durable-functions-youtube3-650w.webp 650w, /assets/images/31.durable-functions-youtube3-960w.webp 960w, /assets/images/31.durable-functions-youtube3-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/31.durable-functions-youtube3-1200w.webp\" width=\"1200\" height=\"750\" alt=\"Durable Functions Youtube\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"the-function-chaining-pattern\"><a href=\"#the-function-chaining-pattern\" class=\"heading-anchor\">The Function Chaining Pattern</a></h2><p>This is the third part of the Durable Functions series. In this video I’ll talk about the function chaining pattern which can be used to create function workflows.</p><p>Here’s the video, please give it a thumbs up if you like it and subscribe to my channel so you’ll be notified of new videos.</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/ARhgG7OeoX0\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><h3 id=\"resources\"><a href=\"#resources\" class=\"heading-anchor\">Resources</a></h3><p>The source code that is used for this demo can be found on <a href=\"https://github.com/marcduiker/demos-azure-durable-functions\" rel=\"noopener\">GitHub</a>.</p>",
      "date_published": "2018-03-06T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/durable-functions-youtube-part2/",
      "url": "https://marcduiker.dev/articles/durable-functions-youtube-part2/",
      "title": "Durable Functions on YouTube (part 2) - Eternal orchestrations &amp; external events",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/30.durable-functions-youtube2-440w.webp 440w, /assets/images/30.durable-functions-youtube2-650w.webp 650w, /assets/images/30.durable-functions-youtube2-960w.webp 960w, /assets/images/30.durable-functions-youtube2-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/30.durable-functions-youtube2-1200w.webp\" width=\"1200\" height=\"750\" alt=\"Durable Functions Youtube\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"eternal-orchestrations-and-external-events\"><a href=\"#eternal-orchestrations-and-external-events\" class=\"heading-anchor\">Eternal orchestrations &amp; external events</a></h2><p>This is the second part of the Durable Functions series. In this video I’ll talk about eternal function orchestrations and external events.</p><p>Here’s the video, please give it a thumbs up if you like it and subscribe to my channel so you’ll be notified of new videos.</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/d73Vl_OHIG4\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><h3 id=\"resources\"><a href=\"#resources\" class=\"heading-anchor\">Resources</a></h3><p>The source code that is used for this demo can be found on <a href=\"https://github.com/marcduiker/demos-azure-durable-functions\" rel=\"noopener\">GitHub</a>.</p>",
      "date_published": "2017-12-01T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/azure-functions-grouping-functions-in-function-apps/",
      "url": "https://marcduiker.dev/articles/azure-functions-grouping-functions-in-function-apps/",
      "title": "Azure Functions Tips: Grouping Functions into Function Apps",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/29.serverless-architecture-440w.webp 440w, /assets/images/29.serverless-architecture-650w.webp 650w, /assets/images/29.serverless-architecture-960w.webp 960w, /assets/images/29.serverless-architecture-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/29.serverless-architecture-1200w.webp\" width=\"1200\" height=\"632\" alt=\"Serverless Architecture\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"guidelines-for-function-apps\"><a href=\"#guidelines-for-function-apps\" class=\"heading-anchor\">Guidelines for Function Apps</a></h2><p>Earlier today I read a tweet where a developer wasn’t sure when to group several Azure Functions in one Function App. The Azure Function engineers responded swiftly and they’ll extend the official docs with some guidelines in this area soon. I think this is a really interesting topic so let’s start with a few ideas of my own.</p><p>Azure Functions are hosted in a Function App. The following is written in the <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference#function-app\" rel=\"noopener\">Azure documentation</a>{:target=“_blank”} about a Function App:</p><p><em>“A function app is comprised of one or more individual functions that are managed together by Azure App Service. All of the functions in a function app share the same pricing plan, continuous deployment and runtime version. Functions written in multiple languages can all share the same function app. Think of a function app as a way to organize and collectively manage your functions.”</em></p><p>Sofar so good but should you put all your functions inside one Function App? Or should you put each function inside its own Function App?</p><p>You’re now dealing with a serverless architecture challenge. Let’s look at some of the aspects involved in order to make an informed decision how to group your functions.</p><h3 id=\"serverless-architecture-aspects\"><a href=\"#serverless-architecture-aspects\" class=\"heading-anchor\">Serverless Architecture Aspects</a></h3><p>Here are a couple of aspects which play a big role in deciding a serverless architecture:</p><ul class=\"list\"><li>Single responsibility principle</li><li>Workload distribution</li><li>DevOps practices</li><li>Resilience</li></ul><h3 id=\"single-responsibility-principle\"><a href=\"#single-responsibility-principle\" class=\"heading-anchor\">Single Responsibility Principle</a></h3><p>I hope this principle is nothing new, the ‘S’ in SOLID. As quoted from <a href=\"(https://en.wikipedia.org/wiki/Single_responsibility_principle)%7B:target=%22_blank%22%7D\">Wikipedia</a>:</p><p><em>“The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.”</em></p><p>In my opinion in this serverless era, a grouping of functions (the Function App) is the equivalent of a module or class as described in the quote.</p><p>I somewhere heard or read the following related to this principle <em>“What changes together should live together”</em>. I adjusted this a bit and prefer <strong>“What changes together should be deployed together”</strong>. So if a new business functionality requires a change of two functions it means those two functions should exist in the same Function App.</p><h3 id=\"workload-distribution\"><a href=\"#workload-distribution\" class=\"heading-anchor\">Workload distribution</a></h3><p>The workload of your functions also determine how they should be grouped. Let’s consider a scenario where you have three functions: A, B, and C. The workload across all of them is evenly distributed and the memory consumption across all of them is below the maximum available memory of the Function App (currently 1.5 GB on the consumption plan). In this case you could run all your functions in the same Function App.</p><p>Now let’s consider another scenario: function A receives a much higher workload than B and C and requires a great deal of the available memory. In this case you should consider moving function A to a separate Function App so it can scale independently when the workload is very high. The low workload functions, B and C, can remain in another Function App and won’t be impacted now by function A.</p><h3 id=\"devops\"><a href=\"#devops\" class=\"heading-anchor\">DevOps</a></h3><p>If you have multiple teams developing, deploying &amp; testing functions then it makes sense to have each team take responsibility of a set of functions in one or more Function Apps. Each team can work independently on their own Function Apps and deployments across teams do not even need to be synced if functions are loosely coupled (e.g. using message queues and agreed interfaces).</p><p>Side note: I also suggest having a code repository per Function App so you can get up and running quickly using the built in <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/functions-continuous-deployment\" rel=\"noopener\">continuous deployment</a>{:target=“_blank”} options. It’s not as complete as a full CI/CD pipeline in VSTS but it’s definitely better than nothing.</p><h3 id=\"resilience\"><a href=\"#resilience\" class=\"heading-anchor\">Resilience</a></h3><p>Downtime is never a good thing right? So when you design your serverless architecture you need to take into account that some components will eventually fail. The App Service can have a hick-up or some ‘bad code’ has somehow got through your CI/CD pipeline and now runs in production. To minimize the impact of the issue you should consider splitting up your functions in several Function Apps to make them <strong>independently deployable</strong> and therefore <strong>independently failable</strong>.</p><p>Again I suggest to use messages queues between functions (either in a separate storage account or using Azure Service Bus) so the state is not lost when a Function App goes down (either through failure or regular deployment). As an alternative <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/durable-functions-overview\" rel=\"noopener\">Durable Functions</a>{:target=“_blank”} offers a built-in mechanism for storing state and looks really promising.</p><h3 id=\"conclusion-it-depends\"><a href=\"#conclusion-it-depends\" class=\"heading-anchor\">Conclusion: It depends</a></h3><p>There is no single answer how to group your functions into Function Apps. But as can be concluded from the described aspects (which are far from exhaustive) it is usually a good idea to keep your Function App lean with only a few closely related functions.</p>",
      "date_published": "2017-11-21T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/durable-functions-youtube-part1/",
      "url": "https://marcduiker.dev/articles/durable-functions-youtube-part1/",
      "title": "Durable Functions on YouTube (part 1)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/28.durable-functions-youtube1-440w.webp 440w, /assets/images/28.durable-functions-youtube1-650w.webp 650w, /assets/images/28.durable-functions-youtube1-960w.webp 960w, /assets/images/28.durable-functions-youtube1-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/28.durable-functions-youtube1-1200w.webp\" width=\"1200\" height=\"750\" alt=\"Durable Functions Youtube\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"durable-functions-videos\"><a href=\"#durable-functions-videos\" class=\"heading-anchor\">Durable Functions Videos</a></h2><p>I’ve decided to create some video’s to explain and demonstrate the <a href=\"https://docs.microsoft.com/azure/azure-functions/durable-functions-overview\" rel=\"noopener\">Durable Functions</a> framework.</p><p>The first video covers a very basic ‘Hello World’ example and shows how to use the <code>OrchestrationClient</code> and <code>DurableOrchestrationContext</code> objects.</p><p>Here’s the video, please give it a thumbs up if you like it and subscribe to my channel so you’ll be notified of new videos.</p><iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/29hX9jZvejE\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe><h3 id=\"resources\"><a href=\"#resources\" class=\"heading-anchor\">Resources</a></h3><p>The source code that is used for this demo can be found on <a href=\"https://github.com/marcduiker/demos-azure-durable-functions\" rel=\"noopener\">GitHub</a>.</p>",
      "date_published": "2017-11-15T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/durable-azure-functions-stateful-orchestrations-part2/",
      "url": "https://marcduiker.dev/articles/durable-azure-functions-stateful-orchestrations-part2/",
      "title": "Azure Durable Functions - Stateful function orchestrations (part 2)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/27.1.durable-functions-demo-440w.webp 440w, /assets/images/27.1.durable-functions-demo-650w.webp 650w, /assets/images/27.1.durable-functions-demo-960w.webp 960w, /assets/images/27.1.durable-functions-demo-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/27.1.durable-functions-demo-1200w.webp\" width=\"1200\" height=\"493\" alt=\"HttpStart Durable Functions\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"durable-function-walkthrough\"><a href=\"#durable-function-walkthrough\" class=\"heading-anchor\">Durable Function Walkthrough</a></h2><p>In my <a href=\"/articles/durable-azure-functions-stateful-orchestrations\">previous post</a> I gave an introduction on Durable Functions, an extension on Azure Functions which can be used to write stateful and long-running orchestration functions.</p><p>In this post we’ll look into more detail at the <code>HttpStart</code> and <code>HelloWorld</code> functions from the previous post. We’ll run them locally by triggering them using <a href=\"https://www.getpostman.com/\" rel=\"noopener\">Postman</a>and looking at the responses.</p><h3 id=\"setup\"><a href=\"#setup\" class=\"heading-anchor\">Setup</a></h3><p>You can start by cloning my <a href=\"https://github.com/marcduiker/demos-azure-durable-functions.git\" rel=\"noopener\">demos-azure-durable-functions</a>GitHub repository. This repo holds the <code>DurableFunctionsDemo</code> Function App which contains the following functions:</p><ul class=\"list\"><li><code>HttpStart</code>, the HttpTrigger function which starts an orchestration function.</li><li><code>HelloWorld</code>, the most basic orchestration function ever.</li><li><code>CollectNames</code>, an ‘eternal’ orchestration function which waits for external events (I’ll cover this one in the next blog post).</li></ul><p>Have a look at the <a href=\"/articles/durable-azure-functions-stateful-orchestrations#developing-durable-functions\">Developing Durable Function</a> section from my previous post in order to make sure you have all the required components to run the functions locally.</p><h3 id=\"running-durable-functions-locally\"><a href=\"#running-durable-functions-locally\" class=\"heading-anchor\">Running Durable Functions Locally</a></h3><p>I usually type <em>azure</em> in the Windows desktop search box to find the Microsoft Azure Storage Emulator and start it from there:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/27.2.azurestorageemulator-started-440w.webp 440w, /assets/images/27.2.azurestorageemulator-started-650w.webp 650w, /assets/images/27.2.azurestorageemulator-started-960w.webp 960w, /assets/images/27.2.azurestorageemulator-started-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/27.2.azurestorageemulator-started-1200w.webp\" width=\"1200\" height=\"479\" alt=\"Azure Storage Emulator is started\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Open the <code>DurableFunctionsDemo.sln</code> solution and press <em>F5</em> to run it locally:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/27.3.functionsruntime1-440w.webp 440w, /assets/images/27.3.functionsruntime1-650w.webp 650w, /assets/images/27.3.functionsruntime1-960w.webp 960w, /assets/images/27.3.functionsruntime1-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/27.3.functionsruntime1-1200w.webp\" width=\"1200\" height=\"515\" alt=\"Local Functions Runtime starting\" loading=\"lazy\" decoding=\"async\"></picture></p><p>After a few seconds you’ll see the local endpoint of the <code>HttpStart</code> function in green:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/27.4.functionsruntime2-440w.webp 440w, /assets/images/27.4.functionsruntime2-650w.webp 650w, /assets/images/27.4.functionsruntime2-960w.webp 960w, /assets/images/27.4.functionsruntime2-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/27.4.functionsruntime2-1200w.webp\" width=\"1200\" height=\"515\" alt=\"Local Functions Runtime is up and running with local endpoint\" loading=\"lazy\" decoding=\"async\"></picture></p><p>You can copy &amp; paste this url into Postman (or any other HTTP API test client) and change the following:</p><ul class=\"list\"><li>Make this a <strong>POST</strong> request</li><li>Change <strong>{functionName}</strong> to <strong>HelloWorld</strong></li><li>In the Body tab, select <strong>raw</strong> and <strong>JSON (application/json)</strong> as the content-type.</li><li>Type a string in the body of the request, such as <strong>“Durable Functions!”</strong>.</li></ul><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/27.5.postman-helloworld-request-440w.webp 440w, /assets/images/27.5.postman-helloworld-request-650w.webp 650w, /assets/images/27.5.postman-helloworld-request-960w.webp 960w, /assets/images/27.5.postman-helloworld-request-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/27.5.postman-helloworld-request-1200w.webp\" width=\"1200\" height=\"315\" alt=\"Request to orchestration/HelloWorld\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Click <em>Send</em> to do the request.</p><p>You might expect to see <strong>“Hello Durable Functions!”</strong> in the response body but that is not the case. You’ll see this instead:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/27.6.postman-helloworld-response-440w.webp 440w, /assets/images/27.6.postman-helloworld-response-650w.webp 650w, /assets/images/27.6.postman-helloworld-response-960w.webp 960w, /assets/images/27.6.postman-helloworld-response-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/27.6.postman-helloworld-response-1200w.webp\" width=\"1200\" height=\"860\" alt=\"Reponse from orchestration/HelloWorld\" loading=\"lazy\" decoding=\"async\"></picture></p><p>This is because you receive the response from the <code>HttpStart</code> function (the <code>DurableOrchestrationClient</code>) and not the <code>HelloWorld</code> function directly.</p><p>The <code>DurableOrchestrationClient</code> class exposes the <code>CreateCheckStatusResponse</code> API which generates an HTTP response containing the HTTP API methods that the client supports. In this response we see the following API methods:</p><ul class=\"list\"><li><code>statusQueryGetUri</code>; when a GET request is made to this endpoint the status of the orchestration function is returned (a serialized <code>Durable​Orchestration​Status</code>).</li><li><code>sendEventPostUri</code>; when a POST request is made to this endpoint (including a valid event name and event data) an event is triggered which can be picked up by an orchestration function. I’ll come back to this in the next blog post.</li><li><code>terminatePostUri</code>; when a POST request is made to this endpoint the orchestration function is stopped without waiting for normal completion.</li></ul><p>The <code>id</code> in the response is the <code>InstanceId</code> of the orchestration function.</p><p>When you click the <code>statusQueryGetUri</code> endpoint and send it as a GET request you’ll get the following response:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/27.7.postman-helloworld-getstatusqueryuri-440w.webp 440w, /assets/images/27.7.postman-helloworld-getstatusqueryuri-650w.webp 650w, /assets/images/27.7.postman-helloworld-getstatusqueryuri-960w.webp 960w, /assets/images/27.7.postman-helloworld-getstatusqueryuri-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/27.7.postman-helloworld-getstatusqueryuri-1200w.webp\" width=\"1200\" height=\"650\" alt=\"Request and response to the statusQueryGetUri endpoint\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Now you have the input &amp; output of the <code>HelloWorld</code> orchestration function and the status of the function.</p><p>Sending requests to the <code>sendEventPostUri</code> and <code>terminatePostUri</code> endpoints don’t make sense in this orchestration function since it’s not listening to external events and it is completed quite fast so terminating it before it reaches completed state is difficult. I’ll get into these two methods in later posts.</p><h4 id=\"looking-under-the-hood\"><a href=\"#looking-under-the-hood\" class=\"heading-anchor\">Looking under the hood</a></h4><p>Finally, if you want to see where the local orchestration function history is kept, (which is used by the Event Sourcing pattern) you can have a look using the <a href=\"https://azure.microsoft.com/en-us/features/storage-explorer/\" rel=\"noopener\">Microsoft Azure Storage Explorer</a>tool. This is a great tool when you’re working with any type of Azure Storage, including local emulations.</p><p>When you navigate to (Local and Attached) &gt; Storage Accounts &gt; Development you’ll see several blob containers, queues and tables which start with <strong>durablefunctionshub</strong>. If you look at the <code>DurableFunctionsHubHistory</code> table you can see all executions of the orchestration functions including the their input and output:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/27.8.storageexplorer-table-440w.webp 440w, /assets/images/27.8.storageexplorer-table-650w.webp 650w, /assets/images/27.8.storageexplorer-table-960w.webp 960w, /assets/images/27.8.storageexplorer-table-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/27.8.storageexplorer-table-1200w.webp\" width=\"1200\" height=\"212\" alt=\"DurableFunctionsHubHistory table\" loading=\"lazy\" decoding=\"async\"></picture></p><h3 id=\"next-steps\"><a href=\"#next-steps\" class=\"heading-anchor\">Next steps</a></h3><p>At this moment you should have gained some insights how to run &amp; debug Durable Functions locally. In the next post I’ll demonstrate an ‘eternal’ orchestration function which waits for external events and is stateful.</p>",
      "date_published": "2017-11-07T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/durable-azure-functions-stateful-orchestrations/",
      "url": "https://marcduiker.dev/articles/durable-azure-functions-stateful-orchestrations/",
      "title": "Azure Durable Functions - Stateful function orchestrations (part 1)",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/26.durable-functions-demo-440w.webp 440w, /assets/images/26.durable-functions-demo-650w.webp 650w, /assets/images/26.durable-functions-demo-960w.webp 960w, /assets/images/26.durable-functions-demo-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/26.durable-functions-demo-1200w.webp\" width=\"1200\" height=\"493\" alt=\"HttpStart DurableFunction\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"durable-functions\"><a href=\"#durable-functions\" class=\"heading-anchor\">Durable Functions</a></h2><p>Since my <a href=\"/articles/serverless-architectures-using-azure-functions\">Getting started with Serverless Architectures using Azure Functions</a> session at Techdays I’ve been closely monitoring the latest news about Azure Functions. The most recent addition is called <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/durable-functions-overview\" rel=\"noopener\">Durable Functions</a>{:target=“_blank”}. With this extension long running and stateful function orchestrations can be developed. This is a welcome addition to the Azure serverless product suite since it is now much easier to implement function chaining and fan-in/fan-out messaging scenarios.</p><h3 id=\"how-does-it-work\"><a href=\"#how-does-it-work\" class=\"heading-anchor\">How does it work?</a></h3><p>Durable Functions are built on top of the <a href=\"https://github.com/Azure/durabletask\" rel=\"noopener\">Durable Task Framework</a>{:target=“_blank”} which enables development of long running persistent workflows by using a pattern called <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/durable-functions-overview#the-technology\" rel=\"noopener\">Event Sourcing</a>{:target=“_blank”}. This pattern ensures that all actions in the orchestration function are stored and can be replayed. One of the benefits of this approach is that when the orchestration function instance has triggered another function to perform a task, the orchestration function itself can hibernate (and will not cost anything when the consumption plan is used) until the other function returns its result.</p><p>Durable Functions use Azure Storage queues, tables and blobs to manage state and messages. It uses the storage account which is created when you create a new Function App through the Azure portal.</p><p>Lets look at the two most important classes in the Durable Functions family; <code>Durable​Orchestration​Client</code> &amp; <code>Durable​Orchestration​Context</code>.</p><h4 id=\"durable​orchestration​client\"><a href=\"#durable​orchestration​client\" class=\"heading-anchor\">Durable​Orchestration​Client</a></h4><p>The <code>Durable​Orchestration​Client</code> class manages orchestration function instances.<br>In order to use this client the following input binding needs to be added:</p><p><code>[OrchestrationClient]DurableOrchestrationClient orchestrationClient</code></p><p>The client exposes the following functionalities:</p><h5 id=\"starting-an-instance\"><a href=\"#starting-an-instance\" class=\"heading-anchor\">Starting an instance</a></h5><p>This will start a new instance of an orchestration function.</p><p><code>string instanceId = await orchestrationClient.StartNewAsync(functionName, eventData);</code></p><p>This <a href=\"https://gist.github.com/marcduiker/e0e7cb5c7eb81614ab5a4470de95d74a\" rel=\"noopener\">Gist</a>{:target=“_blank”} shows an HttpTrigger function which can start orchestration functions using the <code>orchestration/{functionName}</code> route of the Function App:</p><p><strong>HttpStart.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">System<span class=\"token punctuation\">.</span>Net<span class=\"token punctuation\">.</span>Http</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">System<span class=\"token punctuation\">.</span>Threading<span class=\"token punctuation\">.</span>Tasks</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Microsoft<span class=\"token punctuation\">.</span>Azure<span class=\"token punctuation\">.</span>WebJobs</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Microsoft<span class=\"token punctuation\">.</span>Azure<span class=\"token punctuation\">.</span>WebJobs<span class=\"token punctuation\">.</span>Extensions<span class=\"token punctuation\">.</span>Http</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Microsoft<span class=\"token punctuation\">.</span>Azure<span class=\"token punctuation\">.</span>WebJobs<span class=\"token punctuation\">.</span>Host</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DurableFunctionsDemo</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">HttpStart</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">FunctionName</span><span class=\"token attribute-arguments\"><span class=\"token punctuation\">(</span><span class=\"token string\">\"HttpStart\"</span><span class=\"token punctuation\">)</span></span></span><span class=\"token punctuation\">]</span>\n        <span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">async</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span>HttpResponseMessage<span class=\"token punctuation\">&gt;</span></span> <span class=\"token function\">Run</span><span class=\"token punctuation\">(</span>\n            <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">HttpTrigger</span><span class=\"token attribute-arguments\"><span class=\"token punctuation\">(</span>AuthorizationLevel<span class=\"token punctuation\">.</span>Function<span class=\"token punctuation\">,</span> <span class=\"token string\">\"post\"</span><span class=\"token punctuation\">,</span> Route <span class=\"token operator\">=</span> <span class=\"token string\">\"orchestration/{functionName}\"</span><span class=\"token punctuation\">)</span></span></span><span class=\"token punctuation\">]</span><span class=\"token class-name\">HttpRequestMessage</span> req<span class=\"token punctuation\">,</span> \n            <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">OrchestrationClient</span></span><span class=\"token punctuation\">]</span><span class=\"token class-name\">DurableOrchestrationClient</span> orchestrationClient<span class=\"token punctuation\">,</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> functionName<span class=\"token punctuation\">,</span>\n            <span class=\"token class-name\">TraceWriter</span> log<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            log<span class=\"token punctuation\">.</span><span class=\"token function\">Info</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"HttpStart triggered.\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token class-name\"><span class=\"token keyword\">dynamic</span></span> eventData <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> req<span class=\"token punctuation\">.</span>Content<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">ReadAsAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">object</span><span class=\"token punctuation\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> instanceId <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> orchestrationClient<span class=\"token punctuation\">.</span><span class=\"token function\">StartNewAsync</span><span class=\"token punctuation\">(</span>functionName<span class=\"token punctuation\">,</span> eventData<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            log<span class=\"token punctuation\">.</span><span class=\"token function\">Info</span><span class=\"token punctuation\">(</span><span class=\"token interpolation-string\"><span class=\"token string\">$\"Started orchestration with ID = '</span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">instanceId</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">'.\"</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token keyword\">return</span> orchestrationClient<span class=\"token punctuation\">.</span><span class=\"token function\">CreateCheckStatusResponse</span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">,</span> instanceId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><h5 id=\"stopping-an-instance\"><a href=\"#stopping-an-instance\" class=\"heading-anchor\">Stopping an instance</a></h5><p>The orchestration function instance can be terminated without waiting for the results from other functions triggered by the orchestration function instance.</p><p><code>await orchestrationClient.TerminateAsync(instanceId, terminationReason);</code></p><h5 id=\"retrieving-the-status-of-an-instance\"><a href=\"#retrieving-the-status-of-an-instance\" class=\"heading-anchor\">Retrieving the status of an instance</a></h5><p>Since Durable Functions are meant to be long running it is useful to query the status of the orchestration:</p><p><code>var status = await orchestrationClient.GetStatusAsync(instanceId);</code></p><p>The returning type is <code>DurableOrchestrationStatus</code> which contains a <code>RuntimeStatus</code> property indicating if the orchestration function instance is still running, completed or terminated.</p><h5 id=\"raising-events\"><a href=\"#raising-events\" class=\"heading-anchor\">Raising events</a></h5><p>The orchestration client is also capable of raising events:</p><p><code>await orchestrationClient.RaiseEventAsync(instanceId, eventName, eventData);</code></p><p>These events can be picked up by other functions referenced in the orchestration function by using the <code>Durable​Orchestration​Context</code>.</p><h4 id=\"durable​orchestration​context\"><a href=\"#durable​orchestration​context\" class=\"heading-anchor\">Durable​Orchestration​Context</a></h4><p>The <code>DurableOrchestrationContext</code> class is used to call &amp; schedule other functions, sub-orchestrations or wait for events.<br>The orchestration function requires the following input binding:</p><p><code>[OrchestrationTrigger]DurableOrchestrationContext orchestrationContext</code></p><p>This <a href=\"https://gist.github.com/marcduiker/e127deda371260f71ca93b1182e35a85\" rel=\"noopener\">Gist</a> shows the most basic orchestration function, which actually doesn’t do any orchestration, it only retrieves input from the context:</p><p><strong>HelloWorld.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">Microsoft<span class=\"token punctuation\">.</span>Azure<span class=\"token punctuation\">.</span>WebJobs</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Microsoft<span class=\"token punctuation\">.</span>Azure<span class=\"token punctuation\">.</span>WebJobs<span class=\"token punctuation\">.</span>Host</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DurableFunctionsDemo<span class=\"token punctuation\">.</span>Functions</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">HelloWorld</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">FunctionName</span><span class=\"token attribute-arguments\"><span class=\"token punctuation\">(</span><span class=\"token string\">\"HelloWorld\"</span><span class=\"token punctuation\">)</span></span></span><span class=\"token punctuation\">]</span>\n        <span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> <span class=\"token function\">Run</span><span class=\"token punctuation\">(</span>\n            <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">OrchestrationTrigger</span></span><span class=\"token punctuation\">]</span><span class=\"token class-name\">DurableOrchestrationContext</span> context<span class=\"token punctuation\">,</span>\n            <span class=\"token class-name\">TraceWriter</span> log<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> name <span class=\"token operator\">=</span> context<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">GetInput</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">string</span><span class=\"token punctuation\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            log<span class=\"token punctuation\">.</span><span class=\"token function\">Info</span><span class=\"token punctuation\">(</span><span class=\"token interpolation-string\"><span class=\"token string\">$\"HelloWorld function triggered with: </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">name</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">.\"</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token keyword\">return</span> <span class=\"token interpolation-string\"><span class=\"token string\">$\"Hello </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">name</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">\"</span></span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>Although the <code>DurableOrchestrationContext</code> class contains about 15 methods I’ll only describe a few of them in this initial post.</p><h5 id=\"get-function-input\"><a href=\"#get-function-input\" class=\"heading-anchor\">Get function input</a></h5><p>When input is passed to the orchestration function (as JSON) this can be retrieved &amp; deserialized by the <code>GetInput&lt;T&gt;</code> method.</p><p>To retrieve a list of names the following can be used:</p><p><code>var names = orchestrationContext.GetInput&lt;List&lt;string&gt;&gt;()</code></p><h5 id=\"call-another-function\"><a href=\"#call-another-function\" class=\"heading-anchor\">Call another function</a></h5><p>The orchestration function can call other functions using the <code>CallActivityAsync&lt;T&gt;</code> method.</p><p>The following example calls the <em>CollectNames</em> function. Passes an initial list of names (<em>names</em>) and returns a Task of type <code>List&lt;string&gt;</code>:</p><p><code>var collectNamesResult = await context.CallActivityAsync&lt;List&lt;string&gt;&gt;(\"CollectNames\", names);</code></p><h5 id=\"wait-for-an-event\"><a href=\"#wait-for-an-event\" class=\"heading-anchor\">Wait for an event</a></h5><p>As mentioned earlier, the <code>OrchestrationClient</code> can raise events and other functions can react on these by using the <code>WaitForExternalEvent&lt;T&gt;</code> method.</p><p>The following example describes the method to wait on the <em>addname</em> event which has a return type of <code>string</code>:</p><p><code>var addNameResult = await orchestrationContext.WaitForExternalEvent&lt;string&gt;(\"addname\");</code></p><p>When it’s required to await several events use the <code>await Task.WhenAll(...)</code> or <code>Task.WhenAny(...)</code> methods.</p><h5 id=\"restart-the-orchestration\"><a href=\"#restart-the-orchestration\" class=\"heading-anchor\">Restart the orchestration</a></h5><p>When an orchestration is required to be running forever and its full history is not of importance the <code>ContinueAsNew(object input)</code> method can be used to restart the orchestration which resets its history. The current state of orchestration can still be kept because it can be passed to this method and used again at the start of the orchestration using <code>GetInput&lt;T&gt;</code>.</p><p>This example restarts the orchestration function and passing it a list of strings (names):</p><p><code>orchestrationContext.ContinueAsNew(names);</code></p><p>The Durable Functions documentation shows how this can be used in <a href=\"https://docs.microsoft.com/azure/azure-functions/durable-functions-eternal-orchestrations\" rel=\"noopener\">Eternal Orchestrations</a>.</p><h3 id=\"developing-durable-functions\"><a href=\"#developing-durable-functions\" class=\"heading-anchor\">Developing Durable Functions</a></h3><p>I recommend creating compiled orchestration functions using Visual Studio 2017 because currently durable functions can only be developed in C# (support for other languages will follow).</p><p>The following tools/packages are required:</p><ul class=\"list\"><li>The <a href=\"https://docs.microsoft.com/en-us/azure/storage/common/storage-use-emulator\" rel=\"noopener\">Microsoft Azure Storage Emulator</a>{:target=“_blank”} is a standalone tool which uses SQL Server LocalDB and the local file storage instead of Azure Storage. The emulator needs to be started before you can run &amp; debug Durable Functions locally.</li><li>The <a href=\"https://docs.microsoft.com/en-us/azure/azure-functions/functions-develop-vs\" rel=\"noopener\">Azure Functions and Web Jobs Tools</a>{:target=“_blank”} Visual Studio extension. This extension adds a project template to Visual Studio to create Function Apps and run/debug them locally.</li><li>In your Function App you need a reference to this NuGet package: <code>Microsoft.Azure.WebJobs.Extensions.DurableTask</code> (currently 1.0.0-beta).</li></ul><p>I had some issues while adding this package since it has a dependency on <code>Microsoft.Azure.WebJobs</code> <strong>2.1.0-beta4</strong> while the Function App project template uses <strong>2.1.0-beta1</strong>. Make sure when you create a new Function App using the project template you update <code>Microsoft.NET.Sdk.Functions</code> NuGet package to the most recent one (now 1.0.6) so the <code>Microsoft.Azure.WebJobs</code> versions match up.</p><p>Finally make sure you have the following local connection strings in your local.settings.json in your Function App:</p><p><strong>local.settings.json</strong></p><pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n    <span class=\"token property\">\"IsEncrypted\"</span><span class=\"token operator\">:</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"Values\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">\"AzureWebJobsStorage\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://127.0.0.1:10002/\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"AzureWebJobsDashboard\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://127.0.0.1:10002/\"</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><h3 id=\"next-steps\"><a href=\"#next-steps\" class=\"heading-anchor\">Next steps</a></h3><p>I’ve now spent a couple of days tinkering with Durable Functions and I have to say that I enjoy this framework a lot. It’s more powerful than I imagined and although I was a bit skeptical about a more direct coupling of functions by using these orchestration functions I definitely see their value.</p><p>In next posts I’ll share more examples about the <code>DurableOrchestrationContext</code> and include a demo about the orchestration function I wrote.</p><p><strong><a href=\"/articles/durable-azure-functions-stateful-orchestrations-part2\">Continue with Part 2</a></strong></p>",
      "date_published": "2017-11-05T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/serverless-architectures-using-azure-functions/",
      "url": "https://marcduiker.dev/articles/serverless-architectures-using-azure-functions/",
      "title": "Getting started with Serverless Architectures using Azure Functions",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/25.1.marcduiker-on-stage-440w.webp 440w, /assets/images/25.1.marcduiker-on-stage-650w.webp 650w, /assets/images/25.1.marcduiker-on-stage-960w.webp 960w, /assets/images/25.1.marcduiker-on-stage-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/25.1.marcduiker-on-stage-1200w.webp\" width=\"1200\" height=\"674\" alt=\"Marc Duiker on stage at TechDays\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"techdays-2017\"><a href=\"#techdays-2017\" class=\"heading-anchor\">Techdays 2017</a></h2><p>A few weeks ago I was presented with a fantastic opportunity, to speak at the biggest Microsoft conference in the Netherlands, TechDays 2017. The session was about my favorite subjects nowadays, serverless architectures and Azure Functions.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/25.4.marcduiker-presenting-440w.webp 440w, /assets/images/25.4.marcduiker-presenting-650w.webp 650w, /assets/images/25.4.marcduiker-presenting-960w.webp 960w, /assets/images/25.4.marcduiker-presenting-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/25.4.marcduiker-presenting-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Marc Duiker presenting at TechDays\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Below you can find my slides of this session and a link to the demo I made.</p><h3 id=\"slides\"><a href=\"#slides\" class=\"heading-anchor\">Slides</a></h3><iframe src=\"//www.slideshare.net/slideshow/embed_code/key/EmrsTtxYb6Hwgw\" width=\"595\" height=\"485\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" allowfullscreen style=\"border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%\"></iframe><div style=\"margin-bottom:5px\"><strong><a href=\"//www.slideshare.net/marcduiker/getting-started-with-serverless-architectures-using-azure-functions-80755768\" title=\"Getting Started with Serverless Architectures using Azure Functions\" target=\"_blank\">Getting Started with Serverless Architectures using Azure Functions</a> </strong>from <strong><a href=\"https://www.slideshare.net/marcduiker\" target=\"_blank\">Marc Duiker</a></strong></div><h3 id=\"demo\"><a href=\"#demo\" class=\"heading-anchor\">Demo</a></h3><p>The demo solution with two Azure Functions triggered by queues is available on GitHub: <a href=\"https://github.com/marcduiker/demos-serverless-architectures-functions\" rel=\"noopener\">demo-serverless-architectures-functions</a>.</p><p>Let me know if you have any questions or comments when you wan to get started with this.</p><h2 id=\"so-long-and-thanks-for-all-the-fish\"><a href=\"#so-long-and-thanks-for-all-the-fish\" class=\"heading-anchor\">So long, and thanks for all the fish!</a></h2><p>I had a great time at TechDays, I enjoyed speaking as well as attending numerous great sessions. It was a lot of fun being there with all my colleagues.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/25.2.xpirit-team-440w.webp 440w, /assets/images/25.2.xpirit-team-650w.webp 650w, /assets/images/25.2.xpirit-team-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/25.2.xpirit-team-960w.webp\" width=\"960\" height=\"539\" alt=\"Xpirit team at TechDays\" loading=\"lazy\" decoding=\"async\"></picture></p><p>At the end of the conference the two fish which were present at our Xpirit stand did not have a place to go, so I decided to adopt them 😃.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/25.3.marcduiker-with-fish-440w.webp 440w, /assets/images/25.3.marcduiker-with-fish-650w.webp 650w, /assets/images/25.3.marcduiker-with-fish-960w.webp 960w, /assets/images/25.3.marcduiker-with-fish-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/25.3.marcduiker-with-fish-1200w.webp\" width=\"1200\" height=\"1200\" alt=\"Marc Duiker holding a fish bowl\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"want-to-know-more\"><a href=\"#want-to-know-more\" class=\"heading-anchor\">Want to know more?</a></h2><p>If you would like to know more about serverless architectures and Azure Functions, please <a href=\"mailto:mduiker@xpirit.com\">drop me line</a>. I’ll be happy to do a session on this topic at your organization or meetup.</p>",
      "date_published": "2017-10-19T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/installing-python-azure-sdk-raspberrypi/",
      "url": "https://marcduiker.dev/articles/installing-python-azure-sdk-raspberrypi/",
      "title": "Installing the Python Azure SDK on a Raspberry Pi Zero",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/24.pi-zero-with-camera-440w.webp 440w, /assets/images/24.pi-zero-with-camera-650w.webp 650w, /assets/images/24.pi-zero-with-camera-960w.webp 960w, /assets/images/24.pi-zero-with-camera-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/24.pi-zero-with-camera-1200w.webp\" width=\"1200\" height=\"675\" alt=\"Pi Zero with camera module\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"holiday-project\"><a href=\"#holiday-project\" class=\"heading-anchor\">Holiday Project</a></h2><p>This summer holiday I’m working on a hobby project which involves a Raspberry Pi Zero and a Pi camera module. Part of the solution is uploading the pictures the Pi takes to the cloud, Microsoft Azure to be more specific. I plan to write a couple of blog posts about this project. This first post is about installing the Azure SDK on the Pi Zero.</p><h3 id=\"python-and-azure\"><a href=\"#python-and-azure\" class=\"heading-anchor\">Python and Azure</a></h3><p><a href=\"https://www.raspberrypi.org/downloads/raspbian/\" rel=\"noopener\">Raspbian Jessie</a>, the Linux distribution for Raspberry Pi already has Python pre-installed and since Microsoft provides an Azure SDK for Python I decided to write an application in Python which will capture and upload the images to Azure.</p><h3 id=\"errors-when-installing-the-azure-sdk\"><a href=\"#errors-when-installing-the-azure-sdk\" class=\"heading-anchor\">Errors when installing the Azure SDK</a></h3><p>When I tried installing the <a href=\"https://pypi.python.org/pypi/azure\" rel=\"noopener\">Azure SDK</a> (v2.0.0) using:</p><p><code>sudo pip install azure</code></p><p>I got some error messages such as:</p><ul class=\"list\"><li><code>Expected version spec in', 'azure-batch ~=3.0.0'</code></li><li><code>fatal error: ffi.h: No such file or directory</code></li><li><code>fatal error: openssl/opensslv.h: No such file or directory</code></li></ul><p>Apparently the Python environment on my Pi Zero was out of date and missing some libraries. These were the steps I took to get to a successful installation of the Azure SDK:</p><ul class=\"list\"><li>Update pip: <code>sudo pip install --upgrade pip</code></li><li>Install libffi: <code>sudo apt-get install libffi-dev</code></li><li>Install libssl: <code>sudo apt-get install libssl-dev</code></li></ul><h3 id=\"clean-up\"><a href=\"#clean-up\" class=\"heading-anchor\">Clean up</a></h3><p>Since I want to keep my SD card as clean as possible I cleared the cached Python packages located in <code>/var/cache/apt/archives</code> by running this command:</p><p><code>sudo apt-get clean</code></p><p>In the next blog post I’ll show which Azure services I’m using and how I configured them.</p>",
      "date_published": "2017-07-16T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/hands-on-with-sitecore-helix-continuous-delivery-with-octopusdeploy/",
      "url": "https://marcduiker.dev/articles/hands-on-with-sitecore-helix-continuous-delivery-with-octopusdeploy/",
      "title": "Hands-on with Sitecore Helix: Continuous Delivery with Octopus Deploy",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/23.1.Octopus_diagram-440w.webp 440w, /assets/images/23.1.Octopus_diagram-650w.webp 650w, /assets/images/23.1.Octopus_diagram-960w.webp 960w, /assets/images/23.1.Octopus_diagram-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/23.1.Octopus_diagram-1200w.webp\" width=\"1200\" height=\"625\" alt=\"Octopus Deploy process\" loading=\"lazy\" decoding=\"async\"></picture></p><p>In my <a href=\"/articles/hands-on-with-sitecore-helix-setting-up-automated-build-packaging-continuous-delivery\">previous post</a> I described how to setup an automated build for a Sitecore Helix project.<br>In this post I’ll be covering the configuration of the automated deployment environment using <a href=\"https://octopus.com/\" rel=\"noopener\">Octopus Deploy</a>.</p><h2 id=\"octopus-deploy\"><a href=\"#octopus-deploy\" class=\"heading-anchor\">Octopus Deploy</a></h2><p>Octopus Deploy uses the following terminology:</p><ul class=\"list\"><li><em>Environment</em> ; a collection of one or more machines where a release can be deployed to (e.g. <em>Acceptance</em>, which contain two machines, <em>ACC-CM</em> and <em>ACC-CD</em>).</li><li><em>Machine role</em> ; describes the role of a machine (e.g. <em>ContentManagement</em> or <em>ContentDelivery</em>).</li><li><em>Tentacle</em> ; an Octopus service agent running on a deployment target under a specific machine role. The agent communicates with the Octopus server and executes certain deployment steps (e.g. install packages/run scripts).</li><li><em>Lifecycle</em> ; determines the order of promoting a release from one environment to the next. (Also contains the retention policies for the releases.)</li><li><em>Project</em> ; contains the deployment process, the releases and variables.</li><li><em>Deployment Process</em> ; a sequence of steps which controls the deployment of your application.</li><li><em>Variables</em> ; allows the use of variables in deployment steps. This is useful for values which differ between environments or machine roles.</li><li><em>Release</em> ; a version of the deployment process and associated variables.</li></ul><p>Octopus Deploy has <a href=\"http://docs.octopusdeploy.com/display/OD/Getting+started\" rel=\"noopener\">great documentation</a><br>so I won’t be covering all the details.<br>I will discuss the Octopus <em>Deployment Process</em> and <em>Release</em> since these have Sitecore (Helix) specific configurations.</p><h2 id=\"deployment-process\"><a href=\"#deployment-process\" class=\"heading-anchor\">Deployment Process</a></h2><p>A deployment process consists of deployment steps. A step is based on a step template and Octopus provides an<br><a href=\"https://library.octopusdeploy.com/listing\" rel=\"noopener\">extensive library</a> of those.</p><p>The step templates which I use most frequently are <em>Deploy a NuGet package</em> and <em>Add a Script</em>:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/23.2.octopus-most-used-step-templates-440w.webp 440w, /assets/images/23.2.octopus-most-used-step-templates-643w.webp 643w\" sizes=\"90vw\"><img src=\"/assets/images/23.2.octopus-most-used-step-templates-643w.webp\" width=\"643\" height=\"144\" alt=\"Octopus Deploy most used step templates\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The deployment process for our Sitecore Helix project is constructed as follows (<em>PS</em> for <em>Add a Script</em> step, <em>NuGet</em> for <em>Deploy a NuGet package</em> step):</p><ol class=\"list\"><li>IIS AppPool - Stop (<em>PS</em>)</li><li>Remove Unicorn yml files, module configs and module binaries (<em>PS</em>)</li><li>Deploy <code>&lt;Project&gt;.Foundation.&lt;FoundationName&gt;</code> (<em>NuGet</em>, separate step for each Foundation module!)</li><li>Deploy <code>&lt;Project&gt;.Feature.&lt;FeatureName&gt;</code> (<em>NuGet</em>, separate step for each Feature module!)</li><li>Deploy <code>&lt;Project&gt;.Website</code> (<em>NuGet</em>)</li><li>Copy Unicorn yml files to Data folder (<em>PS</em>)</li><li>Remove Unicorn yml files from Website folder (<em>PS</em>)</li><li>Disable/enable config files (<em>PS</em>)</li><li>Update config values (<em>PS</em>)</li><li>IIS AppPool - Start (<em>PS</em>)</li><li>Sync Unicorn items (<em>PS</em>)</li><li>Send notification to Slack (<em>PS</em>)</li></ol><p>The <em>Remove</em> step (2) is recommended because each deployment will only add new files to the website.<br>We’re not installing a vanilla Sitecore from scratch each time.<br>By removing module specific files such as yml, config files and assemblies we can ensure we won’t get unexpected behaviors when files (or complete modules) are removed from the build.</p><h3 id=\"powershell\"><a href=\"#powershell\" class=\"heading-anchor\">PowerShell</a></h3><p>PowerShell steps can use (custom) script modules from the Octopus Library.<br>This allows re-use of script code and keeps the PowerShell steps succinct and easy to understand.</p><p>The following custom made modules are used:</p><ul class=\"list\"><li><em>Remove folder content</em> ; used in step 2 and 7</li><li><em>Copy folder content</em> ; used in step 6</li><li><em>Disable and Enable Config files</em> ; used to enable disable CM/CD specific configs in step 8</li><li><em>Update file content</em> ; used to replace environment and machine role specific tokens in config files in step 9</li><li><em>Sync Unicorn</em> ; used in step 11</li></ul><p>The <em>Disable and Enable Config Files</em> module is useful when dealing with separate content management and delivery servers.<br>I plan to write a blog post about that one separately and to submit the script to the Octopus Library eventually.</p><h2 id=\"release\"><a href=\"#release\" class=\"heading-anchor\">Release</a></h2><p>I’ve setup the release versioning to use the latest available version from the NuGet packages:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/23.3.release-versioning-440w.webp 440w, /assets/images/23.3.release-versioning-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/23.3.release-versioning-650w.webp\" width=\"650\" height=\"75\" alt=\"Release versioning\" loading=\"lazy\" decoding=\"async\"></picture></p><p>A release can be created automatically when Octopus detects a new package being pushed to its NuGet feed.<br>This is configured by selecting a NuGet Package deployment step from the process.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/23.4.automatic-release-creation-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/23.4.automatic-release-creation-440w.webp\" width=\"440\" height=\"256\" alt=\"Automatic release creation\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Although this functionality sounds great I would recommend <strong>not</strong> to use this when dealing with multiple packages!</p><p>When I tried this automatic release creation I experienced that releases often would contain old versions for some packages.<br>This discrepancy was caused by new packages not being available yet on the Octopus NuGet feed when the release was created.</p><p>Octopus triggers the release creation based on <strong>one</strong> NuGet package while a Helix solution will produce many packages (in this setup).<br>So if the package referred to in the <em>Release Creation package step</em> is <strong>not</strong> the last package to be pushed by the build server a release will be created in<br>Octopus which still refers to some old packages. Yikes…</p><p>Since the build order of the projects (and thus the push of the packages to Octopus) might change it is too risky to have the release created automatically<br>based on the presence of one package of which you assume is the latest in the build process.</p><h3 id=\"octoexe\"><a href=\"#octoexe\" class=\"heading-anchor\">Octo.exe</a></h3><p>It is much better to trigger the build by running a command line tool called <code>Octo.exe</code>.<br>This tool needs to be <a href=\"http://docs.octopusdeploy.com/display/OD/Bamboo#Bamboo-Creatingarelease\" rel=\"noopener\">installed on the build server</a> and a post build step needs to be added which calls <code>Octo.exe</code> with the following arguments:</p><p><code>create-release --project &lt;PROJECT_NAME&gt; --version &lt;RELEASE_VERSION&gt; --packageversion &lt;PACKAGE_VERSION&gt; --server &lt;URL_OCTOPUS_SERVER&gt; --apiKey &lt;OCTOPUS_API_KEY&gt; --releaseNotes \"Some notes here about the release\"</code></p><p>Let’s have a look at the available argument switches:</p><ul class=\"list\"><li><code>--project &lt;PROJECT_NAME&gt;</code> specifies the project name in Octopus Deploy to create a new release for.</li><li><code>--version &lt;RELEASE_VERSION&gt;</code> sets the version of the Octopus release (optional).</li><li><code>--packageversion &lt;PACKAGE_VERSION&gt;</code> defines which version of the NuGet packages will be used in the release (optional).</li><li><code>--server &lt;URL_OCTOPUS_SERVER&gt;</code> specifies the Octopus Deploy server URL.</li><li><code>--apiKey &lt;OCTOPUS_API_KEY&gt;</code> specifies the API key which is required to execute the create release command.</li><li><code>--releaseNotes \"Some notes here about the release\"</code> Optional switch for the release notes.</li></ul><p>I’ve omitted the <code>--version</code>and <code>--packageversion</code> switches in the post build job since I let Octopus determine the release number based on the latest NuGet package version available.<br>And since all the NuGet packages are built with the same version number this is safe to use.</p><p>An alternative of using the command line tool would be to use the <a href=\"http://docs.octopusdeploy.com/display/OD/Octopus+REST+API\" rel=\"noopener\">Octopus REST API</a>.<br>The <a href=\"https://github.com/OctopusDeploy/OctopusDeploy-Api/tree/master/REST/PowerShell\" rel=\"noopener\">OctopusDeploy-API GitHub repo</a> contains loads of PowerShell scripts on how to use the REST API.</p><h2 id=\"multiple-nuget-packages-the-good-the-bad-and-the-ugly\"><a href=\"#multiple-nuget-packages-the-good-the-bad-and-the-ugly\" class=\"heading-anchor\">Multiple NuGet packages: The good, the bad and the ugly</a></h2><p>As described in my <a href=\"/articles/hands-on-with-sitecore-helix-continuous-delivery-with-octopusdeploy\">previous post</a> I took the approach of having NuGet packages built for each module (<code>csproj</code>) in our solution.</p><p>The reasons behind this were the following:</p><ol class=\"list\"><li>It is transparent which modules get deployed since that is explicitly configured in the deployment process.</li><li>You have control over which modules will be deployed. There is the option to skip modules but this does introduce a <strong>big</strong> risk when there are dependencies between modules, so be very careful!</li></ol><p>The multiple package approach also has some drawbacks:</p><ol class=\"list\"><li>Many of the created NuGet packages use the same dependencies (e.g. <code>Sitecore.*.dll</code>) which means that there is a lot of duplication of referenced assemblies in these packages. Therefore the total size of the multiple NuGet packages (one for each <code>csproj</code>) is much larger than the size of just one NuGet package for the whole solution. You need to be alert of setting up retention policies on OctopusDeploy and the Tentacles to prevent disk space filling up rapidly (you need to do this anyway but it becomes a problem earlier with many packages).</li><li>When a new module is added to the Visual Studio solution, the deployment process also needs to be updated with an additional NuGet package deployment step. So you actually have a dynamic deployment process which you need to actively maintain. There are ways around this by using a custom Powershell script (see the final section of this post).</li><li>Because there are more packages to deploy the process also takes longer to complete. So if you don’t use a <a href=\"https://martinfowler.com/bliki/BlueGreenDeployment.html\" rel=\"noopener\">blue-green deployment</a><br>strategy you have a longer downtime. The deployment process we have in place now takes about 20 minutes to deploy 48 NuGet packages.</li></ol><h2 id=\"moving-forward\"><a href=\"#moving-forward\" class=\"heading-anchor\">Moving forward</a></h2><p>Will I use the exact same packaging &amp; deployment strategy for the next Sitecore Helix project?</p><p>Probably not. Although I do have (almost) everything in place now I’m not 100% satisfied with the current deployment process.</p><p>I don’t like the fact that whenever a new module has been built a deployment step needs to be added to the Octopus process.<br>Since adding a deployment step is still a manual procedure it might be forgotten and a module will not be deployed. This is quite big risk.</p><p>I’m planning to write a PowerShell script that will locate NuGet packages within Octopus based on a naming convention and deploys these to a configurable target.<br>Then all the current deployment steps for the Feature modules can be replaced with <strong>one</strong> PowerShell script step that will search &amp; deploy packages matching <code>&lt;Project&gt;.Feature.*.nupkg</code>.<br>The Foundation modules can be handled in the same way.</p><p>By just using one NuGet package for the entire solution I wouldn’t have this problem to begin with (but I’m sure I would have had other problems then 😉.</p><p>But which ever route you take for you next Sitecore project, make sure you use a deployment environment such as Octopus because it enables very controlled and reliable deployments.</p><h3 id=\"feedback\"><a href=\"#feedback\" class=\"heading-anchor\">Feedback</a></h3><p>I always appreciate good feedback, so feel free to leave a comment here or discuss this in the <em>helix-habitat</em> channel on <a href=\"https://sitecorechat.slack.com/\" rel=\"noopener\">sitecorechat.slack.com</a>.</p>",
      "date_published": "2017-01-24T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/hands-on-with-sitecore-helix-setting-up-automated-build-packaging-continuous-delivery/",
      "url": "https://marcduiker.dev/articles/hands-on-with-sitecore-helix-setting-up-automated-build-packaging-continuous-delivery/",
      "title": "Hands-on with Sitecore Helix: Setting up automated build and packaging for continuous delivery",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/22.1.helix-logical-architecture-440w.webp 440w, /assets/images/22.1.helix-logical-architecture-650w.webp 650w, /assets/images/22.1.helix-logical-architecture-960w.webp 960w, /assets/images/22.1.helix-logical-architecture-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/22.1.helix-logical-architecture-1200w.webp\" width=\"1200\" height=\"430\" alt=\"Modular architecture\" loading=\"lazy\" decoding=\"async\"></picture></p><p>In my <a href=\"/articles/hands-on-with-sitecore-helix-using-powershell-add-module\">previous</a> <a href=\"/articles/hands-on-with-sitecore-helix-anatomy-add-helix-powershell-script\">posts</a> I’ve shown how our team is able to add new feature or foundation modules easily.<br>Now let’s take a look at the effect of the modular architecture on the build and packaging of a Sitecore Helix style solution.<br>This is a topic which is receiving loads of attention lately and has been blogged about before by other Sitecore <a href=\"https://www.akshaysura.com/2016/12/27/finally-with-one-great-big-gulp-i-conquered-sitecore-helix/\" rel=\"noopener\">community</a> <a href=\"https://www.akshaysura.com/2016/12/28/helix-and-the-re-tooling-of-your-continuous-integration-and-deployments/\" rel=\"noopener\">fanboys</a> 😉.</p><p>First I need to make clear that the solution I will describe is just one of many approaches that can be taken.<br>It is meant to provide insights in the possibilities of building and packaging your Sitecore Helix solution.<br>I will share my concerns of this solution in my next post.</p><h2 id=\"modules-modules-everywhere\"><a href=\"#modules-modules-everywhere\" class=\"heading-anchor\">Modules, Modules Everywhere!</a></h2><p>Carefully read the following paragraph from the <a href=\"http://helix.sitecore.net/principles/architecture-principles/modules.html\" rel=\"noopener\">Helix Architecure Principles</a>:</p><blockquote><p>“Keep in mind that in Helix, modules are business-centric. This means that they should relate to business objectives and group together multiple technology entities that refer to this objective.<br>This principle goes against many traditional software conventions - such as the ones dictated by MVC (models, controllers and views) or even Sitecore (templates, layouts, settings) - that define grouping based on their type, rather than their business objective.”</p></blockquote><p>The Sitecore Habitat solution currently consists of:</p><ul class=\"list\"><li>16 feature modules</li><li>14 foundation modules</li><li>2 project modules</li></ul><p>The Visual Studio solution has 54 projects (incl test projects) in total.<br>The real world Sitecore project I’m working on at the moment has even more.<br>It is definitely a mind shift to work with large numbers of projects.<br>Be cautious not create a module for each user story on your backlog though.<br>You can of course group or consolidate components which are very similar into one feature.</p><h3 id=\"performance-vs-clarity\"><a href=\"#performance-vs-clarity\" class=\"heading-anchor\">Performance vs Clarity</a></h3><p><a href=\"http://sitecore.stackexchange.com/questions/3623/sitecore-helix-habitat-and-visual-studio-structure\" rel=\"noopener\">Some people</a> are against this high number of projects in one Visual Studio solution.<br>Although a large number of projects does have a negative impact on Visual Studio performance I encourage the usage of this pattern.<br>This is because the clarity of this modular architecture outweighs the performance issue.<br>Identifying modules is so straightforward now and this greatly helps the communication about these feature and foundation modules.</p><p>As a developer you really need some decent hardware to make this work efficiently in Visual Studio.<br>If your development machine is slow, do <a href=\"https://docs.google.com/spreadsheets/d/16tzObRLEdgszbxU-un4lG6K-shiE5c39K95aSfrlXvI/edit?usp=sharing\" rel=\"noopener\">some calculations</a> so you can make a business case which supports your request to get a new machine.</p><p>So what do you need to do when you have a whole team producing loads of modules? Well, build &amp; package those modules in order to deploy to other<br>environments of course so testers and end-users can marvel at your work.</p><h2 id=\"continuous-integration-and-delivery\"><a href=\"#continuous-integration-and-delivery\" class=\"heading-anchor\">Continuous Integration &amp; Delivery</a></h2><p>You are all using source control and a centralized build environment, right?</p><p>Currently I’m using <a href=\"https://about.gitlab.com/\" rel=\"noopener\">GitLab</a> for source control and <a href=\"https://www.atlassian.com/software/bamboo\" rel=\"noopener\">Bamboo</a> as the build environment.<br>Although Bamboo is not one of my preferred build platforms it does the job well enough.<br>In addition <a href=\"https://octopus.com/\" rel=\"noopener\">Octopus Deploy</a> is used as the automated deployment environment (more of that in the next post).</p><p>Let’s have a closer look at how Visual Studio projects are configured.</p><h3 id=\"visual-studio-project-configuration\"><a href=\"#visual-studio-project-configuration\" class=\"heading-anchor\">Visual Studio Project Configuration</a></h3><p>By default Visual Studio gives you two solution configurations: <code>Debug</code> and <code>Release</code>.<br>Depending on how you have your environment specific configuration setup you might have more configurations but our team only using these two now.</p><p>The <code>Debug</code> configuration is the default on during local development. The <code>Release</code> configuration is used for the centralized build (more on that later).</p><p>I noticed that even in <code>Release</code> mode *.pdb (symbol) files are created during a build.<br>If you want your release build to have a small footprint (and without debugging capabilities) you can disable to creation of these files by setting the <em>DebugInfo</em> to <code>none</code> under <em>Build</em> &gt; <em>Adanced…</em>:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/22.2.advanced-build-settings-440w.webp 440w, /assets/images/22.2.advanced-build-settings-650w.webp 650w, /assets/images/22.2.advanced-build-settings-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/22.2.advanced-build-settings-960w.webp\" width=\"960\" height=\"449\" alt=\"Set DebugInfo to None\" loading=\"lazy\" decoding=\"async\"></picture></p><h4 id=\"packaging-with-octopack\"><a href=\"#packaging-with-octopack\" class=\"heading-anchor\">Packaging with Octopack</a></h4><p>Octopus provides a <strong>very</strong> easy way of packaging a solution or projects as NuGet packages through the use of <a href=\"http://docs.octopusdeploy.com/display/OD/Using+OctoPack\" rel=\"noopener\">Octopack</a>.<br>In our solution each Visual Studio project has a reference to Octopack.<br>This means that instead of one solution package there are be many packages (one per module).<br>This approach does has some drawbacks in the deployment process which I’m not too happy about as I will explain in the next post.</p><h4 id=\"nuspec\"><a href=\"#nuspec\" class=\"heading-anchor\">Nuspec</a></h4><p>A nuspec file contains package metadata and specifies the folders &amp; files will be included in a NuGet package. Octopack can use these nuspec files when NuGet packages are created.</p><p>However you don’t <strong>need</strong> to provide a nuspec file if you’re happy with what Visual Studio includes during a build (check the <em>Build Action</em> in the <em>Properties</em> pane if a file is included or not).</p><p>But if you want files included which are <strong>not</strong> included in Visual Studio projects, such as the serialized Sitecore <code>yml</code> files in our case, you need to explicitly add that to the nuspec file.<br>See line 15 in this nuspec template gist:</p><p><strong><em>NamespacePrefix</em>.<em>ModuleType</em>.<em>Name</em>.nuspec</strong></p><pre class=\"language-xml\"><code class=\"language-xml\"><span class=\"token prolog\">&lt;?xml version=\"1.0\"?&gt;</span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>package</span> <span class=\"token attr-name\">xmlns</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">&gt;</span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>metadata</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>id</span><span class=\"token punctuation\">&gt;</span></span>_NamespacePrefix_._ModuleType_._Name_<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>id</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>title</span><span class=\"token punctuation\">&gt;</span></span>_NamespacePrefix_._ModuleType_._Name_<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>title</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>version</span><span class=\"token punctuation\">&gt;</span></span>$version$<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>version</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>authors</span><span class=\"token punctuation\">&gt;</span></span>_Company_<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>authors</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>owners</span><span class=\"token punctuation\">&gt;</span></span>_Company_<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>owners</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>requireLicenseAcceptance</span><span class=\"token punctuation\">&gt;</span></span>false<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>requireLicenseAcceptance</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>description</span><span class=\"token punctuation\">&gt;</span></span>_NamespacePrefix_._ModuleType_._Name_<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>description</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>releaseNotes</span><span class=\"token punctuation\">&gt;</span></span>Automatic build<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>releaseNotes</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>copyright</span><span class=\"token punctuation\">&gt;</span></span>Copyright 2016 _Company_<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>copyright</span><span class=\"token punctuation\">&gt;</span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>metadata</span><span class=\"token punctuation\">&gt;</span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>files</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>file</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>..\\serialization\\**<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">target</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Unicorn\\_ModuleType_\\_Name_\\serialization<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/&gt;</span></span>\n        <span class=\"token comment\">&lt;!-- Only include roles when they are required per component.\n        &lt;file src=\"..\\roles\\**\" target=\"Unicorn\\_ModuleType_\\_Name_\\roles\" /&gt;\n        --&gt;</span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>files</span><span class=\"token punctuation\">&gt;</span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>package</span><span class=\"token punctuation\">&gt;</span></span></code></pre><p>Three things might not be too obvious in this nuspec and are worth highlighting:</p><ol class=\"list\"><li>You can move out of the project/solution directory by using the relative path notation <code>..</code>.</li><li>You need to type <code>**</code> in order to include subfolder content.</li><li>I only added <strong>one</strong> <code>&lt;file&gt;</code> line for the yml files. The standard build output from Visual Studio will also be packaged up but that is configured through a Octopack specific build parameter (<code>OctoPackEnforceAddingFiles=true</code>) which I’ll describe in the Build section in a moment.</li></ol><p>Everything is setup now in Visual Studio. Let’s move on to the build server.</p><h3 id=\"bamboo\"><a href=\"#bamboo\" class=\"heading-anchor\">Bamboo</a></h3><p>The build plans in are Bamboo are quite straightforward. A plan consists of one stage having one job which has the following three tasks:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/22.3.bamboo-tasks-432w.webp 432w\" sizes=\"90vw\"><img src=\"/assets/images/22.3.bamboo-tasks-432w.webp\" width=\"432\" height=\"194\" alt=\"Bamboo tasks\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Currently there are two build plans:</p><ol class=\"list\"><li>One which checks out the <code>develop</code> branch and does a build in <code>Debug</code> configuration. This plan runs every 15 mins and when does a build when changes are detected in the repository.</li><li>One which checks out the <code>master</code> branch and performs a build in <code>Release</code> configuration. This plan is triggered manually. During the build it will create NuGet packages and pushes them to an internal Octopus Deploy NuGet feed.</li></ol><p>I just want to focus on building the solution in <code>Release</code> configuration since that part is different since we use the Helix approach nowadays.</p><h4 id=\"build-package-and-publish\"><a href=\"#build-package-and-publish\" class=\"heading-anchor\">Build, Package &amp; Publish</a></h4><p>The MSBuild task looks as follows:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/22.4.msbuild-task-440w.webp 440w, /assets/images/22.4.msbuild-task-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/22.4.msbuild-task-650w.webp\" width=\"650\" height=\"546\" alt=\"MSBuild task\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The <em>Options</em> field is the most interesting (I’ve replaced some sensitive info):</p><p><code>/p:Configuration=Release;RunOctoPack=true;OctoPackEnforceAddingFiles=true;OctoPackPackageVersion=1.0.${bamboo.buildNumber};OctoPackPublishPackageToHttp=&lt;URL_TO_OCTOPUS_NUGET&gt;;OctoPackPublishApiKey=&lt;OCTOPUS_API_KEY&gt;</code></p><p>Let’s have a detailed look at the build parameters:</p><ul class=\"list\"><li><code>Configuration=Release</code> is obvious I hope.</li><li><code>RunOctoPack=true</code> means that Octopack will be run for each of the projects where it has been added as a NuGet reference.</li><li><code>OctoPackEnforceAddingFiles=true</code> ensures that the NuGet package contains both the build output files <strong>and</strong> the files specified in the nuspec.</li><li><code>OctoPackPackageVersion=1.0.${bamboo.buildNumber}</code> sets the version number of the NuGet package based on the Bamboo build number.</li><li><code>OctoPackPublishPackageToHttp=&lt;URL_TO_OCTOPUS_NUGET&gt;</code> defines the URL of the Octopus NuGet feed where created packages will be published to.</li><li><code>OctoPackPublishApiKey=&lt;OCTOPUS_API_KEY&gt;</code> contains the API key which is required when publishing to the Octopus NuGet feed.</li></ul><p>Now we’re all set to run a build which will publish the NuGet packages for all the modules to Octopus Deploy.<br>In the next blogpost I’ll go into detail how the deployment process is setup in Octopus.</p><p>As already mentioned by my Sitecore community friend <a href=\"https://twitter.com/akshaysura13\" rel=\"noopener\">Akshay</a>:</p><blockquote><p>“I encourage you to discuss Helix/Habitat based conversations in the <a href=\"https://sitecorechat.slack.com\" rel=\"noopener\">Sitecore Slack Helix-Habitat channel</a>.”</p></blockquote>",
      "date_published": "2017-01-03T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/hands-on-with-sitecore-helix-anatomy-add-helix-powershell-script/",
      "url": "https://marcduiker.dev/articles/hands-on-with-sitecore-helix-anatomy-add-helix-powershell-script/",
      "title": "Hands-on with Sitecore Helix: Anatomy of the Add-HelixModule.ps1 PowerShell script",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/21.add_feature_script-440w.webp 440w, /assets/images/21.add_feature_script-650w.webp 650w, /assets/images/21.add_feature_script-833w.webp 833w\" sizes=\"90vw\"><img src=\"/assets/images/21.add_feature_script-833w.webp\" width=\"833\" height=\"493\" alt=\"Add-Feature PowerShell function\" loading=\"lazy\" decoding=\"async\"></picture></p><p>In my <a href=\"/articles/hands-on-with-sitecore-helix-using-powershell-add-module\">previous post</a> I<br>showed how I got to a solution which allows the developers in my team to create new Feature and Foundation modules with ease.</p><p>I showed the moving parts of the solution but I did not go into much detail of the most important part so that’s what I’ll do in this post.<br>This would be particularly useful if you want to change the script yourself to match it to your needs.</p><h2 id=\"a-detailed-look-at-add-helixmoduleps1\"><a href=\"#a-detailed-look-at-add-helixmoduleps1\" class=\"heading-anchor\">A detailed look at add-helixmodule.ps1</a></h2><p>The <code>add-helixmodule.ps1</code> script is where all the action happens. The file is <a href=\"https://github.com/marcduiker/Habitat/blob/master/scripts/add-helixmodule.ps1\" rel=\"noopener\">included in my Habitat fork</a> and is also available as a gist which is shown inline below.</p><p>I’ve added loads of comments to it today, so I think should give you enough to work with.<br>The function which handles the addition of projects to the solution through the <a href=\"https://msdn.microsoft.com/en-us/library/envdte.dte.aspx\" rel=\"noopener\">DTE interface</a> is called <code>Add-Projects</code> (how surprising!) and starts at line 283.</p><p>Please do let me know if you have comments or suggestions for improvements!</p><p><strong>add-helixmodule.ps1</strong></p><pre class=\"language-powershell\"><code class=\"language-powershell\"><span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    This script contains the Add-Feature and Add-Foundation methods which can be used to add a new module to a Sitecore Helix based Visual Studio solution.\n    \n    The Visual Studio solution should contain a add-helix-module-configuration.json file containing variables which this script will use.\n    \n    The Add-Feature and Add-Foundation methods can be run from the Pacakge Console Manager as long as this script is loaded in the relevant PowerShell profile. \n    Run $profile in the Pacakge Manager Console to verify the which profile is used.\n#&gt;</span>\n\n<span class=\"token comment\"># Some hardcoded values</span>\n<span class=\"token variable\">$featureModuleType</span> = <span class=\"token string\">\"Feature\"</span>                                      <span class=\"token comment\"># Used in Add-Feature and Create-Config.</span>\n<span class=\"token variable\">$foundationModuleType</span> = <span class=\"token string\">\"Foundation\"</span>                                <span class=\"token comment\"># Used in Add-Foundation and Create-Config.</span>\n<span class=\"token variable\">$addHelixModuleConfigFile</span> = <span class=\"token string\">\"add-helix-module-configuration.json\"</span>   <span class=\"token comment\"># Used in Add-Module.</span>\n<span class=\"token variable\">$csprojExtension</span> = <span class=\"token string\">\".csproj\"</span>                                        <span class=\"token comment\"># Used in Add-Projects</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    Creates a config object which is used in the other functions in this script file.\n\n    .DESCRIPTION\n    This function should be considered private and is called from the Add-Module function.\n\n    .Parameter JsonConfigFilePath\n    The path of the json based configuration file which contains the path to the module-template folder,\n    namespaces and tokens to replace.\n\n    .Parameter ModuleType\n    The type of the new module, either 'Feature' or 'Foundation'.\n\n    .Parameter ModuleName\n    The name of the new module, excluding namespaces since these are retreived from the config object. \n\n    .Parameter SolutionRootFolder\n    The path to the folder which contains the Visual Studio solution (sln) file.\n\n#&gt;</span>\n<span class=\"token keyword\">function</span> Create-Config\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">Param</span><span class=\"token punctuation\">(</span>\n        <span class=\"token namespace\">[Parameter(Position=0, Mandatory=$True)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$JsonConfigFilePath</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=1, Mandatory=$True)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$ModuleType</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=2, Mandatory=$True)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$ModuleName</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=3, Mandatory=$True)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$SolutionRootFolder</span>\n    <span class=\"token punctuation\">)</span>\n\n    <span class=\"token variable\">$jsonFile</span> = <span class=\"token function\">Get-Content</span> <span class=\"token operator\">-</span>Raw <span class=\"token operator\">-</span>Path <span class=\"token string\">\"<span class=\"token variable\">$JsonConfigFilePath</span>\"</span> <span class=\"token punctuation\">|</span> <span class=\"token function\">ConvertFrom-Json</span>\n    \n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token variable\">$config</span> = <span class=\"token function\">New-Object</span> psobject\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name ModuleTemplatePath <span class=\"token operator\">-</span>Value <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>moduleTemplatePath <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name SourceFolderName <span class=\"token operator\">-</span>Value <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>sourceFolderName <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name TemplateNamespacePrefix <span class=\"token operator\">-</span>Value <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>templateNamespacePrefix <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name TemplateModuleType <span class=\"token operator\">-</span>Value <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>templateModuleType <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name TemplateModuleName <span class=\"token operator\">-</span>Value <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>templateModuleName <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name TemplateProjectGuid <span class=\"token operator\">-</span>Value <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>templateProjectGuid <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name TemplateTestProjectGuid <span class=\"token operator\">-</span>Value <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>templateTestProjectGuid <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name FileExtensionsToUpdateContentRegex <span class=\"token operator\">-</span>Value <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>fileExtensionsToUpdateContentRegex <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name FileExtensionsToUpdateProjectGuidsRegex <span class=\"token operator\">-</span>Value <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>fileExtensionsToUpdateProjectGuidsRegex <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name ModuleType <span class=\"token operator\">-</span>Value <span class=\"token variable\">$ModuleType</span> <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name ModuleName <span class=\"token operator\">-</span>Value <span class=\"token variable\">$ModuleName</span> <span class=\"token operator\">-</span>MemberType NoteProperty\n        \n        <span class=\"token comment\"># GUIDs are needed for the VS projects</span>\n        <span class=\"token variable\">$projectGuid</span> = <span class=\"token namespace\">[guid]</span>::NewGuid<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>toString<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>toUpper<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name ProjectGuid <span class=\"token operator\">-</span>Value <span class=\"token variable\">$projectGuid</span> <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token variable\">$testProjectGuid</span> = <span class=\"token namespace\">[guid]</span>::NewGuid<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>toString<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>toUpper<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name TestProjectGuid <span class=\"token operator\">-</span>Value <span class=\"token variable\">$testProjectGuid</span> <span class=\"token operator\">-</span>MemberType NoteProperty\n        \n        <span class=\"token comment\"># The json config file contains two namespace prefixes. One for Foundation modules and one for Feature modules.</span>\n        <span class=\"token comment\"># This seperation is done to allow namespace differentiation between those module types. </span>\n        <span class=\"token comment\"># Foundation modules could be reusable across development projects while Feature module most likely will not. </span>\n        <span class=\"token variable\">$newNamespacePrefix</span> = <span class=\"token string\">\"\"</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token variable\">$ModuleType</span> <span class=\"token operator\">-eq</span> <span class=\"token variable\">$featureModuleType</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token variable\">$newNamespacePrefix</span> = <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>featureNamespacePrefix\n        <span class=\"token punctuation\">}</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token variable\">$ModuleType</span> <span class=\"token operator\">-eq</span> <span class=\"token variable\">$foundationModuleType</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token variable\">$newNamespacePrefix</span> = <span class=\"token variable\">$jsonFile</span><span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>foundationNamespacePrefix\n        <span class=\"token punctuation\">}</span>\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name NamespacePrefix <span class=\"token operator\">-</span>Value <span class=\"token variable\">$newNamespacePrefix</span> <span class=\"token operator\">-</span>MemberType NoteProperty\n        <span class=\"token function\">Add-Member</span> <span class=\"token operator\">-</span>InputObject <span class=\"token variable\">$config</span> <span class=\"token operator\">-</span>Name SolutionRootFolder <span class=\"token operator\">-</span>Value <span class=\"token variable\">$SolutionRootFolder</span> <span class=\"token operator\">-</span>MemberType NoteProperty\n\n        <span class=\"token keyword\">return</span> <span class=\"token variable\">$config</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    The main function that calls the other rename* functions.\n\n    .DESCRIPTION\n    This function should be considered private and is called from the Add-Module function.\n\n    .PARAMETER StartPath\n    The full path of the new module folder. This is used as a path to start folder and file searches.\n\n#&gt;</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">Rename-Module</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">Param</span><span class=\"token punctuation\">(</span>\n        <span class=\"token namespace\">[Parameter(Position=0, Mandatory=$True)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$StartPath</span>\n    <span class=\"token punctuation\">)</span>\n\n    <span class=\"token comment\"># Rename all the folders from the copied module-template.</span>\n    <span class=\"token function\">Rename-Folders</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>OldValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateModuleType <span class=\"token operator\">-</span>NewValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleType\n    <span class=\"token function\">Rename-Folders</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>OldValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateModuleName <span class=\"token operator\">-</span>NewValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleName\n\n    <span class=\"token comment\"># Rename all the files from the copied module-template.</span>\n    <span class=\"token function\">Rename-Files</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>OldValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateNamespacePrefix <span class=\"token operator\">-</span>NewValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>NamespacePrefix\n    <span class=\"token function\">Rename-Files</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>OldValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateModuleType <span class=\"token operator\">-</span>NewValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleType\n    <span class=\"token function\">Rename-Files</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>OldValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateModuleName <span class=\"token operator\">-</span>NewValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleName\n\n    <span class=\"token comment\"># Update file content for GUIDs.</span>\n    <span class=\"token function\">Update-FileContent</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>OldValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateProjectGuid <span class=\"token operator\">-</span>NewValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ProjectGuid <span class=\"token operator\">-</span>FileExtensionsRegex <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>fileExtensionsToUpdateProjectGuidsRegex\n    <span class=\"token function\">Update-FileContent</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>OldValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateTestProjectGuid <span class=\"token operator\">-</span>NewValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TestProjectGuid <span class=\"token operator\">-</span>FileExtensionsRegex <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>fileExtensionsToUpdateProjectGuidsRegex\n    \n    <span class=\"token comment\"># Update file content for namespaces, module tpyes and module name.</span>\n    <span class=\"token function\">Update-FileContent</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>OldValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateNamespacePrefix <span class=\"token operator\">-</span>NewValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>NamespacePrefix <span class=\"token operator\">-</span>FileExtensionsRegex <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>FileExtensionsToUpdateContentRegex\n    <span class=\"token function\">Update-FileContent</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>OldValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateModuleType <span class=\"token operator\">-</span>NewValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleType <span class=\"token operator\">-</span>FileExtensionsRegex <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>FileExtensionsToUpdateContentRegex\n    <span class=\"token function\">Update-FileContent</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>OldValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateModuleName <span class=\"token operator\">-</span>NewValue <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleName <span class=\"token operator\">-</span>FileExtensionsRegex <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>FileExtensionsToUpdateContentRegex\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    Renames files, replaces OldValue with NewValue in the filename. \n\n    .DESCRIPTION\n    This function should be considered private and is called from the Rename-Module function.\n\n    .PARAMETER StartPath\n    The full path of the new module folder. This is used as a path to start folder and file searches.\n\n    .PARAMETER OldValue\n    The part of the filename which is used to search and is replaced with NewValue.\n\n    .PARAMETER NewValue\n    The value which is used in the replacement of OldValue.\n\n#&gt;</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">Rename-Files</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">Param</span><span class=\"token punctuation\">(</span>\n        <span class=\"token namespace\">[Parameter(Position=0, Mandatory=$true)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$StartPath</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=1, Mandatory=$true)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$OldValue</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=2, Mandatory=$true)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$NewValue</span>\n    <span class=\"token punctuation\">)</span>\n\n    <span class=\"token variable\">$pattern</span> = <span class=\"token string\">\"*<span class=\"token variable\">$OldValue</span>*\"</span>\n    <span class=\"token function\">Write-Output</span> <span class=\"token string\">\"Renaming <span class=\"token variable\">$pattern</span> files located in <span class=\"token variable\">$StartPath</span>.\"</span>\n    <span class=\"token variable\">$fileItems</span> = <span class=\"token function\">Get-ChildItem</span> <span class=\"token operator\">-</span>File <span class=\"token operator\">-</span>Path <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span><span class=\"token keyword\">Filter</span> <span class=\"token variable\">$pattern</span> <span class=\"token operator\">-</span>Recurse <span class=\"token operator\">-</span>Force <span class=\"token punctuation\">|</span> <span class=\"token function\">Where-Object</span> <span class=\"token punctuation\">{</span> <span class=\"token variable\">$_</span><span class=\"token punctuation\">.</span>FullName <span class=\"token operator\">-notmatch</span> <span class=\"token string\">\"\\\\(obj|bin)\\\\?\"</span> <span class=\"token punctuation\">}</span> \n    <span class=\"token variable\">$fileItems</span> <span class=\"token punctuation\">|</span> <span class=\"token function\">Rename-Item</span> <span class=\"token operator\">-</span>NewName <span class=\"token punctuation\">{</span> <span class=\"token variable\">$_</span><span class=\"token punctuation\">.</span>Name <span class=\"token operator\">-replace</span> <span class=\"token variable\">$OldValue</span><span class=\"token punctuation\">,</span> <span class=\"token variable\">$NewValue</span> <span class=\"token punctuation\">}</span> <span class=\"token operator\">-</span>Force\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    Renames folders, replaces OldValue with NewValue in the folder name. \n\n    .DESCRIPTION\n    This function should be considered private and is called from the Rename-Module function.\n\n    .PARAMETER StartPath\n    The full path of the new module folder. This is used as a path to start folder and file searches.\n\n    .PARAMETER OldValue\n    The part of the folder name which is used to search and is replaced with NewValue.\n\n    .PARAMETER NewValue\n    The value which is used in the replacement of OldValue.\n\n#&gt;</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">Rename-Folders</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">Param</span><span class=\"token punctuation\">(</span>\n        <span class=\"token namespace\">[Parameter(Position=0, Mandatory=$true)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$StartPath</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=1, Mandatory=$true)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$OldValue</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=2, Mandatory=$true)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$NewValue</span>\n    <span class=\"token punctuation\">)</span>\n\n    <span class=\"token variable\">$pattern</span> = <span class=\"token string\">\"*<span class=\"token variable\">$OldValue</span>*\"</span>\n    <span class=\"token function\">Write-Output</span> <span class=\"token string\">\"Renaming <span class=\"token variable\">$pattern</span> folders located in <span class=\"token variable\">$StartPath</span>.\"</span>\n    <span class=\"token comment\"># Note the usage of Sort-Object { $_.FullName.Length } -Descending. </span>\n    <span class=\"token comment\"># This is done to prevent exceptions with nested folders that need to be renamed.</span>\n    <span class=\"token comment\"># Folders are renamed from lowest level to highest level. </span>\n    <span class=\"token variable\">$folderItems</span> = <span class=\"token function\">Get-ChildItem</span> <span class=\"token operator\">-</span>Directory <span class=\"token operator\">-</span>Path <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>Recurse <span class=\"token operator\">-</span><span class=\"token keyword\">Filter</span> <span class=\"token variable\">$pattern</span> <span class=\"token operator\">-</span>Force <span class=\"token punctuation\">|</span> <span class=\"token function\">Where-Object</span> <span class=\"token punctuation\">{</span> <span class=\"token variable\">$_</span><span class=\"token punctuation\">.</span>FullName <span class=\"token operator\">-notmatch</span> <span class=\"token string\">\"\\\\(obj|bin)\\\\?\"</span> <span class=\"token punctuation\">}</span> <span class=\"token punctuation\">|</span> <span class=\"token function\">Sort-Object</span> <span class=\"token punctuation\">{</span> <span class=\"token variable\">$_</span><span class=\"token punctuation\">.</span>FullName<span class=\"token punctuation\">.</span>Length <span class=\"token punctuation\">}</span> <span class=\"token operator\">-</span>Descending\n    <span class=\"token variable\">$folderItems</span> <span class=\"token punctuation\">|</span> <span class=\"token function\">Rename-Item</span> <span class=\"token operator\">-</span>NewName <span class=\"token punctuation\">{</span> <span class=\"token variable\">$_</span><span class=\"token punctuation\">.</span>Name <span class=\"token operator\">-replace</span> <span class=\"token variable\">$OldValue</span><span class=\"token punctuation\">,</span> <span class=\"token variable\">$NewValue</span> <span class=\"token punctuation\">}</span> <span class=\"token operator\">-</span>Force\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    Updates the content of files, replaces OldValue with NewValue. \n\n    .DESCRIPTION\n    This function should be considered private and is called from the Rename-Module function.\n\n    .PARAMETER StartPath\n    The full path of the new module folder. This is used as a path to start folder and file searches.\n\n    .PARAMETER OldValue\n    The part of the filename which is used to search and is replaced with NewValue.\n\n    .PARAMETER NewValue\n    The value which is used in the replacement of OldValue.\n\n    .PARAMETER FileExtensionsRegex\n    A regular expression that describes which file extensions are searched for.\n\n#&gt;</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">Update-FileContent</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">Param</span><span class=\"token punctuation\">(</span>\n        <span class=\"token namespace\">[Parameter(Position=0, Mandatory=$true)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$StartPath</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=1, Mandatory=$true)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$OldValue</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=2, Mandatory=$true)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$NewValue</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=3, Mandatory=$true)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$FileExtensionsRegex</span>\n    <span class=\"token punctuation\">)</span>\n\n    <span class=\"token function\">Write-Output</span> <span class=\"token string\">\"Renaming <span class=\"token variable\">$OldValue</span> to <span class=\"token variable\">$NewValue</span> in files matching <span class=\"token variable\">$FileExtensionsRegex</span> located in <span class=\"token variable\">$StartPath</span>.\"</span>\n\n    <span class=\"token variable\">$filesToUpdate</span> = <span class=\"token function\">Get-ChildItem</span> <span class=\"token operator\">-</span>File <span class=\"token operator\">-</span>Path <span class=\"token string\">\"<span class=\"token variable\">$StartPath</span>\"</span> <span class=\"token operator\">-</span>Recurse <span class=\"token operator\">-</span>Force <span class=\"token punctuation\">|</span> <span class=\"token function\">Where-Object</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">(</span> <span class=\"token variable\">$_</span><span class=\"token punctuation\">.</span>FullName <span class=\"token operator\">-notmatch</span> <span class=\"token string\">\"\\\\(obj|bin)\\\\?\"</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-and</span> <span class=\"token punctuation\">(</span><span class=\"token variable\">$_</span><span class=\"token punctuation\">.</span>Name <span class=\"token operator\">-match</span> <span class=\"token variable\">$FileExtensionsRegex</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span> <span class=\"token punctuation\">|</span> <span class=\"token function\">Select-String</span> <span class=\"token operator\">-</span>Pattern <span class=\"token variable\">$OldValue</span> <span class=\"token punctuation\">|</span> <span class=\"token function\">Group-Object</span> Path <span class=\"token punctuation\">|</span> <span class=\"token function\">Select-Object</span> <span class=\"token operator\">-</span>ExpandProperty Name\n    \n    <span class=\"token comment\"># -ireplace: case insensitive replacement</span>\n    <span class=\"token variable\">$filesToUpdate</span> <span class=\"token punctuation\">|</span> <span class=\"token function\">ForEach-Object</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">(</span><span class=\"token function\">Get-Content</span> <span class=\"token variable\">$_</span> <span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span>ireplace <span class=\"token namespace\">[regex]</span>::Escape<span class=\"token punctuation\">(</span><span class=\"token variable\">$OldValue</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token variable\">$NewValue</span> <span class=\"token punctuation\">|</span> <span class=\"token function\">Set-Content</span> <span class=\"token variable\">$_</span> <span class=\"token operator\">-</span>Force <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    Returns the path of the new module.\n\n    .DESCRIPTION\n    The path is constructed as follows: SolutionRootFolder\\SourceFolderName\\ModuleType\\ModuleName.\n    This function should be considered private and is called from the Add-Module function.\n#&gt;</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">Get-ModulePath</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token variable\">$sourceFolderPath</span> =  <span class=\"token function\">Join-Path</span> <span class=\"token operator\">-</span>Path <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>SolutionRootFolder <span class=\"token operator\">-</span>ChildPath <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>SourceFolderName\n    <span class=\"token variable\">$moduleTypePath</span> = <span class=\"token function\">Join-Path</span> <span class=\"token operator\">-</span>Path <span class=\"token string\">\"<span class=\"token variable\">$sourceFolderPath</span>\"</span> <span class=\"token operator\">-</span>ChildPath <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleType\n    <span class=\"token variable\">$modulePath</span> = <span class=\"token function\">Join-Path</span> <span class=\"token operator\">-</span>Path <span class=\"token string\">\"<span class=\"token variable\">$moduleTypePath</span>\"</span> <span class=\"token operator\">-</span>ChildPath <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleName\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">Test-Path</span> <span class=\"token variable\">$modulePath</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">throw</span> <span class=\"token namespace\">[System.ArgumentException]</span> <span class=\"token string\">\"<span class=\"token variable\">$modulePath</span> already exists.\"</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">return</span> <span class=\"token variable\">$modulePath</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    Helper function to retrieve the literal 'Feature' or 'Foundation' solution folder.\n\n    .DESCRIPTION\n    This function should be considered private and is called from the Add-Projects function.\n#&gt;</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">Get-ModuleTypeSolutionFolder</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token variable\">$dte</span><span class=\"token punctuation\">.</span>Solution<span class=\"token punctuation\">.</span>Projects <span class=\"token punctuation\">|</span> <span class=\"token function\">Where-Object</span> <span class=\"token punctuation\">{</span> <span class=\"token variable\">$_</span><span class=\"token punctuation\">.</span>Name <span class=\"token operator\">-eq</span> <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleType <span class=\"token operator\">-and</span> <span class=\"token variable\">$_</span><span class=\"token punctuation\">.</span>Kind <span class=\"token operator\">-eq</span> <span class=\"token namespace\">[EnvDTE80.ProjectKinds]</span>::vsProjectKindSolutionFolder <span class=\"token punctuation\">}</span> <span class=\"token punctuation\">|</span> <span class=\"token function\">Select-Object</span> <span class=\"token operator\">-</span>First 1\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    Adds new module project(s) to the solution.\n    \n    .DESCRIPTION\n    Searches for csproj files in the new module folder and uses EnvDTE80 interfaces to add these to the solution.\n    This function should be considered private and is called from the Add-Module function.\n#&gt;</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">Add-Projects</span>\n<span class=\"token punctuation\">{</span>\n     <span class=\"token keyword\">Param</span><span class=\"token punctuation\">(</span>\n        <span class=\"token namespace\">[Parameter(Position=0, Mandatory=$True)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$ModulePath</span>\n    <span class=\"token punctuation\">)</span>\n\n    <span class=\"token function\">Write-Output</span> <span class=\"token string\">\"Adding project(s)...\"</span>\n    <span class=\"token variable\">$moduleTypeFolder</span> = <span class=\"token function\">Get-ModuleTypeSolutionFolder</span>\n    <span class=\"token function\">Write-Output</span> <span class=\"token variable\">$moduleTypeFolder</span>\n\n    <span class=\"token comment\"># When the literal 'Feature' or 'Foundation' solution folder does not exist in the solution it will be created. </span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">-not</span> <span class=\"token variable\">$moduleTypeFolder</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token variable\">$dte</span><span class=\"token punctuation\">.</span>Solution<span class=\"token punctuation\">.</span>AddSolutionFolder<span class=\"token punctuation\">(</span><span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleType<span class=\"token punctuation\">)</span>\n        <span class=\"token variable\">$moduleTypeFolder</span> = <span class=\"token function\">Get-ModuleTypeSolutionFolder</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token variable\">$folderInterface</span> = <span class=\"token function\">Get-Interface</span> <span class=\"token variable\">$moduleTypeFolder</span><span class=\"token punctuation\">.</span>Object <span class=\"token punctuation\">(</span><span class=\"token namespace\">[EnvDTE80.SolutionFolder]</span><span class=\"token punctuation\">)</span>\n    <span class=\"token variable\">$moduleNameFolder</span> = <span class=\"token variable\">$folderInterface</span><span class=\"token punctuation\">.</span>AddSolutionFolder<span class=\"token punctuation\">(</span><span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleName<span class=\"token punctuation\">)</span>\n    <span class=\"token variable\">$moduleNameInterface</span> = <span class=\"token function\">Get-Interface</span> <span class=\"token variable\">$moduleNameFolder</span><span class=\"token punctuation\">.</span>Object <span class=\"token punctuation\">(</span><span class=\"token namespace\">[EnvDTE80.SolutionFolder]</span><span class=\"token punctuation\">)</span>\n    \n    <span class=\"token comment\"># Search in the new module folder for csproj files and add those to the solution.</span>\n    <span class=\"token function\">Get-ChildItem</span> <span class=\"token operator\">-</span>File <span class=\"token operator\">-</span>Path <span class=\"token variable\">$ModulePath</span> <span class=\"token operator\">-</span><span class=\"token keyword\">Filter</span> <span class=\"token string\">\"*<span class=\"token variable\">$csprojExtension</span>\"</span> <span class=\"token operator\">-</span>Recurse <span class=\"token punctuation\">|</span> <span class=\"token function\">ForEach-Object</span> <span class=\"token punctuation\">{</span> <span class=\"token variable\">$moduleNameInterface</span><span class=\"token punctuation\">.</span>AddFromFile<span class=\"token punctuation\">(</span><span class=\"token string\">\"<span class=\"token function\">$<span class=\"token punctuation\">(</span><span class=\"token variable\">$_</span><span class=\"token punctuation\">.</span>FullName<span class=\"token punctuation\">)</span></span>\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span>\n    <span class=\"token function\">Write-Output</span> <span class=\"token string\">\"Saving solution...\"</span>\n    \n    <span class=\"token comment\"># Strangely enough the Solution interface does not contain a simple Save() method so a call to SaveAs(fileName) with the filename needs to be done.</span>\n    <span class=\"token variable\">$dte</span><span class=\"token punctuation\">.</span>Solution<span class=\"token punctuation\">.</span>SaveAs<span class=\"token punctuation\">(</span><span class=\"token variable\">$dte</span><span class=\"token punctuation\">.</span>Solution<span class=\"token punctuation\">.</span>FullName<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    Main function to add a new module.\n\n    .DESCRIPTION\n    This function should be considered private and is called from the Add-Feature or Add-Foundation function.\n\n    .PARAMETER ModuleName\n    The name of the new module.\n\n    .PARAMETER ModuleType\n    The type of the new module, either 'Feature' or 'Foundation'.\n#&gt;</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">Add-Module</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">Param</span><span class=\"token punctuation\">(</span>\n        <span class=\"token namespace\">[Parameter(Position=0, Mandatory=$True)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$ModuleName</span><span class=\"token punctuation\">,</span>\n        <span class=\"token namespace\">[Parameter(Position=1, Mandatory=$True)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$ModuleType</span>\n    <span class=\"token punctuation\">)</span>\n    \n    <span class=\"token keyword\">try</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token comment\"># Do a check if there is a solution active in Visual Studio.</span>\n        <span class=\"token comment\"># If there is no active solution the Add-Projects function would fail.</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">-not</span> <span class=\"token variable\">$dte</span><span class=\"token punctuation\">.</span>Solution<span class=\"token punctuation\">.</span>FullName<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">throw</span> <span class=\"token namespace\">[System.ArgumentException]</span> <span class=\"token string\">\"There is no active solution. Load a Sitecore Helix solution first which contains an <span class=\"token variable\">$addHelixModuleConfigFile</span> file.\"</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token comment\"># The only reason I do this check is because I need a path to start searching for the json based config file. </span>\n        <span class=\"token variable\">$solutionRootFolder</span> = <span class=\"token namespace\">[System.IO.Path]</span>::GetDirectoryName<span class=\"token punctuation\">(</span><span class=\"token variable\">$dte</span><span class=\"token punctuation\">.</span>Solution<span class=\"token punctuation\">.</span>FullName<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">-not</span> <span class=\"token punctuation\">(</span><span class=\"token function\">Test-Path</span> <span class=\"token string\">\"<span class=\"token variable\">$solutionRootFolder</span>\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">throw</span> <span class=\"token namespace\">[System.IO.DirectoryNotFoundException]</span> <span class=\"token string\">\"<span class=\"token variable\">$solutionRootFolder</span> folder not found.\"</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token variable\">$configJsonFile</span> = <span class=\"token function\">Get-ChildItem</span> <span class=\"token operator\">-</span>Path <span class=\"token string\">\"<span class=\"token variable\">$solutionRootFolder</span>\"</span> <span class=\"token operator\">-</span>File <span class=\"token operator\">-</span><span class=\"token keyword\">Filter</span> <span class=\"token string\">\"<span class=\"token variable\">$addHelixModuleConfigFile</span>\"</span> <span class=\"token operator\">-</span>Recurse <span class=\"token punctuation\">|</span> <span class=\"token function\">Select-Object</span> <span class=\"token operator\">-</span>First 1 <span class=\"token punctuation\">|</span> <span class=\"token function\">Select-Object</span> <span class=\"token operator\">-</span>ExpandProperty FullName\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">-not</span> <span class=\"token punctuation\">(</span><span class=\"token function\">Test-Path</span> <span class=\"token variable\">$configJsonFile</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">throw</span> <span class=\"token namespace\">[System.IO.DirectoryNotFoundException]</span> <span class=\"token string\">\"<span class=\"token variable\">$configJsonFile</span> not found.\"</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token comment\"># Create a config object we can use throughout the other functions.</span>\n        <span class=\"token variable\">$config</span> = Create-Config <span class=\"token operator\">-</span>JsonConfigFilePath <span class=\"token string\">\"<span class=\"token variable\">$configJsonFile</span>\"</span> <span class=\"token operator\">-</span>ModuleType <span class=\"token variable\">$ModuleType</span> <span class=\"token operator\">-</span>ModuleName <span class=\"token variable\">$ModuleName</span> <span class=\"token operator\">-</span>SolutionRootFolder <span class=\"token variable\">$solutionRootFolder</span>\n        \n        <span class=\"token comment\"># Get the path to the module-template folder and verify that is exists on disk.</span>\n        <span class=\"token variable\">$copyModuleFromLocation</span> = <span class=\"token function\">Join-Path</span> <span class=\"token operator\">-</span>Path <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>ModuleTemplatePath <span class=\"token operator\">-</span>ChildPath <span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>TemplateModuleName\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">-not</span> <span class=\"token punctuation\">(</span><span class=\"token function\">Test-Path</span> <span class=\"token variable\">$copyModuleFromLocation</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">throw</span> <span class=\"token namespace\">[System.IO.DirectoryNotFoundException]</span> <span class=\"token string\">\"<span class=\"token variable\">$copyModuleFromLocation</span> folder not found.\"</span>\n        <span class=\"token punctuation\">}</span>\n        \n        <span class=\"token variable\">$modulePath</span> = <span class=\"token function\">Get-ModulePath</span>\n        <span class=\"token function\">Write-Output</span> <span class=\"token string\">\"Copying module template to <span class=\"token variable\">$modulePath</span>.\"</span>\n        <span class=\"token function\">Copy-Item</span> <span class=\"token operator\">-</span>Path <span class=\"token string\">\"<span class=\"token variable\">$copyModuleFromLocation</span>\"</span> <span class=\"token operator\">-</span>Destination <span class=\"token string\">\"<span class=\"token variable\">$modulePath</span>\"</span> <span class=\"token operator\">-</span>Recurse\n        <span class=\"token function\">Rename-Module</span> <span class=\"token operator\">-</span>StartPath <span class=\"token string\">\"<span class=\"token variable\">$modulePath</span>\"</span>\n        <span class=\"token function\">Add-Projects</span> <span class=\"token operator\">-</span>ModulePath <span class=\"token string\">\"<span class=\"token variable\">$modulePath</span>\"</span>\n\n        <span class=\"token function\">Write-Output</span> <span class=\"token string\">\"Completed adding <span class=\"token function\">$<span class=\"token punctuation\">(</span><span class=\"token variable\">$config</span><span class=\"token punctuation\">.</span>NamespacePrefix<span class=\"token punctuation\">)</span></span>.<span class=\"token variable\">$moduleType</span>.<span class=\"token variable\">$moduleName</span>.\"</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">catch</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token function\">Write-Error</span> <span class=\"token variable\">$error</span><span class=\"token punctuation\">[</span>0<span class=\"token punctuation\">]</span>\n        <span class=\"token keyword\">exit</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    Adds a Sitecore Helix Feature module to the current solution.\n    \n    .DESCRIPTION\n    The solution should contain an add-helix-module-configuration.json file containing \n    paths to the module template folder and namespace settings for the new module. \n\n    .PARAMETER Name\n    The name of the new Feature, excluding the namespace prefix since that comes from the json config file.\n\n    .EXAMPLE\n    Add-Feature Navigation\n\n#&gt;</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">Add-Feature</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">Param</span><span class=\"token punctuation\">(</span>\n        <span class=\"token namespace\">[Parameter(Position=0, Mandatory=$True)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$Name</span>\n    <span class=\"token punctuation\">)</span>\n\n    <span class=\"token function\">Add-Module</span> <span class=\"token operator\">-</span>ModuleName <span class=\"token variable\">$Name</span> <span class=\"token operator\">-</span>ModuleType <span class=\"token variable\">$featureModuleType</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">&lt;#\n    .SYNOPSIS\n    Adds a Sitecore Helix Foundation module to the current solution.\n    \n    .DESCRIPTION\n    The solution should contain an add-helix-module-configuration.json file containing \n    paths to the module template folder and namespace settings for the new module. \n\n    .PARAMETER Name\n    The name of the new Foundation module, excluding the namespace prefix since that comes from the json config file.\n\n    .EXAMPLE\n    Add-Foundation Dictionary\n\n#&gt;</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">Add-Foundation</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">Param</span><span class=\"token punctuation\">(</span>\n        <span class=\"token namespace\">[Parameter(Position=0, Mandatory=$True)]</span>\n        <span class=\"token namespace\">[string]</span><span class=\"token variable\">$Name</span>\n    <span class=\"token punctuation\">)</span>\n\n    <span class=\"token function\">Add-Module</span> <span class=\"token operator\">-</span>ModuleName <span class=\"token variable\">$Name</span> <span class=\"token operator\">-</span>ModuleType <span class=\"token variable\">$foundationModuleType</span>\n<span class=\"token punctuation\">}</span></code></pre>",
      "date_published": "2016-12-29T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/hands-on-with-sitecore-helix-using-powershell-add-module/",
      "url": "https://marcduiker.dev/articles/hands-on-with-sitecore-helix-using-powershell-add-module/",
      "title": "Hands-on with Sitecore Helix: Using PowerShell to add a new module",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/20.1.powershell_vs_sitecore_helix-440w.webp 440w, /assets/images/20.1.powershell_vs_sitecore_helix-588w.webp 588w\" sizes=\"90vw\"><img src=\"/assets/images/20.1.powershell_vs_sitecore_helix-588w.webp\" width=\"588\" height=\"251\" alt=\"PowerShell plus Visual Studio equals Sitecore Helix\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"embracing-sitecore-helix\"><a href=\"#embracing-sitecore-helix\" class=\"heading-anchor\">Embracing Sitecore Helix</a></h2><p>Ever since I attended <a href=\"https://twitter.com/AndersLaub\" rel=\"noopener\">Anders Laub</a> his presentation at SUGCON Europe 2015 about component based architecture in Sitecore solutions I have been a strong advocate of these modular principles.<br>I even went to <a href=\"http://www.pentia.net\" rel=\"noopener\">Pentia</a> to learn about this in full detail.</p><p>I was very happy to see that Sitecore finally got their act together and published their <a href=\"http://helix.sitecore.net/\" rel=\"noopener\">Helix</a> guidelines and recommended practices on the web.</p><p>For the last half year my team is using the modular Helix style architecture with success and it’s time to share some experiences.</p><p><strong>Go straight to the <a href=\"#tldr-my-add-helixmodule-solution\">TLDR</a></strong></p><h2 id=\"adding-a-new-module-with-ease\"><a href=\"#adding-a-new-module-with-ease\" class=\"heading-anchor\">Adding a new module with ease</a></h2><p>Seeing a Sitecore Helix solution can be a bit daunting at first.<br>The folder structure is quite deeply nested and developers need to have a good understanding what a module is composed of.<br>Adding a new Feature or Foundation module to your Sitecore Helix solution is a time consuming and error prone task if you do it manually over and over again.<br>Just as a learning experience it’s good to know what needs to be done so please <a href=\"https://www.youtube.com/watch?v=4lC-SdYh4Xg\" rel=\"noopener\">do have a look</a>.</p><p>Since adding a module is such a repetative task it is a perfect candidate for automation. Currently there are two <a href=\"http://yeoman.io/\" rel=\"noopener\">Yeoman</a> based solutions to create modules; <a href=\"https://github.com/kamsar/generator-habitat\" rel=\"noopener\"><code>generator-habitat</code></a> and <a href=\"https://github.com/mrodriguezr/generator-prodigious-helix\" rel=\"noopener\"><code>generator-prodigious-helix</code></a>.</p><h3 id=\"pros\"><a href=\"#pros\" class=\"heading-anchor\">Pros</a></h3><p>Both these Yeoman generators allow you to create a new Feature or Foundation module with ease. Both create the folder structure and the Visual Studio projects.<br>The main difference is that <code>generator-habitat</code> works with <a href=\"https://github.com/kamsar/Unicorn\" rel=\"noopener\">Unicorn</a> and <code>generator-prodigious-helix</code> works with <a href=\"http://www.teamdevelopmentforsitecore.com/\" rel=\"noopener\">TDS</a>.</p><h3 id=\"cons\"><a href=\"#cons\" class=\"heading-anchor\">Cons</a></h3><p>The only drawback of both generators is that they don’t update the solution file.<br>So you manually need to add the generated projects into he solution (and create the module specific solution folder as well).</p><p>Because I’m <a href=\"http://threevirtues.com/\" rel=\"noopener\">lazy</a> and don’t want to do repetitive work I set out to find another solution for this.</p><h2 id=\"powershell\"><a href=\"#powershell\" class=\"heading-anchor\">PowerShell</a></h2><p>Since I’m more familiair with PowerShell I used that instead of the Yeoman generators (I already invested quite some time in my own solution before I became aware of the Yeoman generators).<br>Fairly quickly I had a script that would copy a template folder to the desired destination and replace tokens for the module name, namespaces and GUIDs.</p><p>The only thing left was adding the projects to the solution.<br>First I took a very basic approach and started parsing the sln file since it’s all plain text anyway.<br>But it became quite a hassle to manage project relations and nesting of projects and solution folders with only GUIDs to work with.<br>I needed a better solution. And then I met DTE.</p><h2 id=\"dte-to-the-rescue\"><a href=\"#dte-to-the-rescue\" class=\"heading-anchor\">DTE to the rescue</a></h2><p><a href=\"http://stackoverflow.com/questions/17239760/what-is-the-visual-studio-dte\" rel=\"noopener\"><code>DTE</code></a> (Development Tools Environment) or <code>EnvDTE</code> is the <a href=\"https://msdn.microsoft.com/en-us/library/envdte._dte.aspx\" rel=\"noopener\">Visual Studio automation model</a> and is used for Visual Studio extensions to manipulate the solution and it’s projects. The <code>DTE</code> framework (COM based) is implemented across several <code>EnvDTE*.dll</code> and <code>VSLangProj*.dll</code> libraries depending on the version of Visual Studio you’re running.</p><p>The <a href=\"https://msdn.microsoft.com/en-us/library/envdte80.solutionfolder.aspx\" rel=\"noopener\"><code>SolutionFolder</code></a> interface in the <code>EnvDTE80</code> assembly captured my interest with the following methods:</p><ul class=\"list\"><li><a href=\"https://msdn.microsoft.com/en-us/library/envdte80.solutionfolder.addsolutionfolder.aspx\" rel=\"noopener\"><code>AddSolutionFolder</code></a></li><li><a href=\"https://msdn.microsoft.com/en-us/library/envdte80.solutionfolder.addfromfile.aspx\" rel=\"noopener\"><code>AddFromFile</code></a></li></ul><p>In my initial attempts of using DTE I experienced quite some difficulties in creating the right types of objects and interfaces.<br>This was probably due to not having the correct <code>EnvDTE*.dll</code> and <code>VSLangProj*.dll</code> assemblies loaded in my PowerShell script.</p><p>I found that the NuGet Package Manager Console in Visual Studio already has the proper assemblies loaded since it’s also using <code>DTE</code> when adding new NuGet packages to the solution.<br>Now I only needed to find a way to call my PowerShell script from the Package Manager Console.</p><h2 id=\"nuget-profile\"><a href=\"#nuget-profile\" class=\"heading-anchor\">NuGet profile</a></h2><p>The PowerShell commands that can be used in the Package Manager Console are stored in a <code>Profile.ps1</code> script located at <code>C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\IDE\\Extensions\\&lt;someweirdID&gt;\\Modules\\NuGet</code>.<br>Since that profile belongs to the console it’s probably best to not touch that one because it could be overwritten during an update.</p><p>When you type <code>$profile</code> in the Package Manager Console you’ll get the location of a user profile that can be used to extend the default one.<br>In my case I got the following:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/20.2.nuget_profile_path-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/20.2.nuget_profile_path-440w.webp\" width=\"440\" height=\"103\" alt=\"NuGet Profile Path\" loading=\"lazy\" decoding=\"async\"></picture></p><p>If you go to that location there might not be a profile file at all. You can then create an empty file and name it <code>NuGet_profile.ps1</code>.</p><p>When you make changes to this user profile while Visual Studio is open Visual Studio will not detect any changes. You can type <code>&amp; $profile</code> in the Package Manager Console to reload the profile.</p><h2 id=\"tldr-my-add-helixmodule-solution\"><a href=\"#tldr-my-add-helixmodule-solution\" class=\"heading-anchor\">TLDR: My Add-HelixModule Solution</a></h2><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/20.3.add_helixmodule-440w.webp 440w, /assets/images/20.3.add_helixmodule-650w.webp 650w, /assets/images/20.3.add_helixmodule-960w.webp 960w, /assets/images/20.3.add_helixmodule-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/20.3.add_helixmodule-1200w.webp\" width=\"1200\" height=\"633\" alt=\"Add-HelixModule\" loading=\"lazy\" decoding=\"async\"></picture></p><ol class=\"list\"><li><code>module-template</code>, a folder containing code and config template files. This template is based on a Sitecore Habitat Feature which is stripped down significantly.</li><li><code>add-helixmodule.ps1</code>, a PowerShell script which creates a new Feature or Foundation module <strong>AND</strong> adds this to the current solution in Visual Studio.</li><li><code>NuGet_profile.ps1</code>, a PowerShell NuGet profile that is used by the Visual Studio Package Manager Console. This profile only loads the add-helixmodule.ps1.</li><li><code>add-helix-module-configuration.json</code>, a config file containing values for namespaces, location of the module-template and more.</li></ol><p>You can see it working in my fork of Sitecore Habitat:</p><ul class=\"list\"><li>Clone my <a href=\"https://github.com/marcduiker/Habitat\" rel=\"noopener\">Sitecore Habitat fork</a>.</li><li>Verify that you have a <code>NuGet_profile.ps1</code> (use <code>$profile</code> to check the location).</li><li>Add the following to this profile and update the path to point to the <code>add-helixmodule.ps1</code> file on your disk:</li></ul><p><strong>NuGet_profile.ps1</strong></p><pre class=\"language-powershell\"><code class=\"language-powershell\"><span class=\"token comment\">&lt;# \n    Loads the add-helixmodule.ps1 script to enable the creation of Feature and Foundation project in Sitecore Helix solutions.\n    \n    You need to change this path to the location where the script is located on your local machine. \n    \n    Once the script is loaded the Add-Feature and Add-Foundation methods are available in the Package Manager Console in Visual Studio.\n#&gt;</span>\n<span class=\"token punctuation\">.</span> <span class=\"token string\">\"C:\\dev\\git\\HabitatFork\\scripts\\add-helixmodule.ps1\"</span></code></pre><ul class=\"list\"><li>Open the Sitecore Habitat solution in Visual Studio.</li><li>Open the <code>add-helix-module-configuration.json</code> file.</li><li>Update the following values in that configuration file:<ul class=\"list\"><li><code>moduleTemplatePath</code>. This is the absolute path to the module-template folder.</li><li><code>featureNamespacePrefix</code>. This is the namespace prefix for new Feature modules (e.g. CompanyName.ClientName).</li><li><code>foundationNamespacePrefix</code>. This is the namespace prefix for new Foundation modules (e.g. CompanyName).</li><li><code>sourceFolderName</code>. This is the relative path to the location where the Feature/Foundation and Project folders are. For Sitecore Habitat this is <code>//src</code>.</li></ul></li></ul><p><strong>add-helix-module-configuration.json</strong></p><pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n\t<span class=\"token property\">\"__comment\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"This configuration file is used by the add-helix-module.ps1 script which creates modules for Sitecore Helix solutions.\"</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token property\">\"config\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token property\">\"__comment__moduleTemplatePath\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Update the moduleTemplatePath property to point to your module-template location.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"moduleTemplatePath\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"C:\\\\dev\\\\git\\\\HabitatFork\\\\module-template\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"__comment__featureNamespacePrefix\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Replace the value for featureNamespacePrefix with a suitable namespace prefix. The Feature.&lt;ModuleName&gt; will be appended by the script.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"featureNamespacePrefix\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"CompanyNamespace.ClientNamespace\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"__comment__foundationNamespacePrefix\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Replace the value for featureNamespacePrefix with a suitable namespace prefix. The Foundation.&lt;ModuleName&gt; will be appended by the script.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"foundationNamespacePrefix\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"CompanyNamespace\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"__comment__sourceFolderName\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"The sourcefolder should contain the relative path (from the sln file folder) where the Feature, Foundation and Project folders are located. The Sitecore Habitat default is '\\\\src'.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"sourceFolderName\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\\\\src\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"__comment__fileExtensionsToUpdateContentRegex\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"The regex in the fileExtensionsToUpdateContentRegex property is used to find the files which contain tokens which will be replaced with new values by the script.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"fileExtensionsToUpdateContentRegex\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"(.config|.csproj|.cs|.cshtml|.feature|.js|.nuspec|.role|.sitecore|.targets)$\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"__comment__fileExtensionsToUpdateProjectGuidsRegex\"</span> <span class=\"token operator\">:</span> <span class=\"token string\">\"The regex in the fileExtensionsToUpdateProjectGuidsRegex property is used to find the files which need to have VS project GUIDs inserted.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"fileExtensionsToUpdateProjectGuidsRegex\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"(.csproj|AssemblyInfo.cs)$\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"__comment__\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"The templateNamespacePrefix property contains the token which will be replaced with the value from either featureNamespacePrefix or foundationNamespacePrefix.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"templateNamespacePrefix\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"_NamespacePrefix_\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"__comment__templateModuleType\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"The templateModuleType property contains the token variable which will be replaced with Feature or Foundation.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"templateModuleType\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"_ModuleType_\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"__comment__templateModuleName\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"The templateModuleName property contains the token which will be replace with the actual ModuleName.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"templateModuleName\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"_Name_\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"__comment__templateProjectGuid\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"The templateProjectGuid property contains the token which will be replaced by a new GUID and used as VS module project identifier.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"templateProjectGuid\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"_ProjectGuid_\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"__comment__templateTestProjectGuid\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"The templateTestProjectGuid property contains the token which will be replaced by a new GUID and used as VS test project identifier.\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"templateTestProjectGuid\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"_TestProjectGuid_\"</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><ul class=\"list\"><li>Type <code>Add-Feature</code> or <code>Add-Foundation</code> in the Package Manager Console followed by the name of the module and hit enter.</li></ul><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/20.4.add_feature_completed-440w.webp 440w, /assets/images/20.4.add_feature_completed-650w.webp 650w, /assets/images/20.4.add_feature_completed-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/20.4.add_feature_completed-960w.webp\" width=\"960\" height=\"638\" alt=\"Add-Feature Completed\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/20.5.solution_explorer-440w.webp 440w, /assets/images/20.5.solution_explorer-636w.webp 636w\" sizes=\"90vw\"><img src=\"/assets/images/20.5.solution_explorer-636w.webp\" width=\"636\" height=\"258\" alt=\"Solution Explorer showing the added feature\" loading=\"lazy\" decoding=\"async\"></picture></p><p>In my <a href=\"/articles/hands-on-with-sitecore-helix-anatomy-add-helix-powershell-script\">next post</a> I’ll dig deeper<br>into the inner workings of the <code>add-helixmodule.ps1</code> PowerShell script.</p><h2 id=\"so-are-we-done-now\"><a href=\"#so-are-we-done-now\" class=\"heading-anchor\">So are we done now?</a></h2><p>No. Currently the script only works with an existing Visual Studio solution that uses the Helix/Habitat folder structure so it heavily relies on folder names called <code>Feature</code> and <code>Foundation</code>.</p><p>Improvements I can think of now:</p><ul class=\"list\"><li>Make the script more robust/configurable so it works with other naming conventions instead on Feature/Foundation.</li><li>Add yml files for rendering and template folders similar as <code>generator-habitat</code> is doing.</li><li>Extend the script so it could also create a whole new Sitecore Helix based solution.</li><li>Use separate template structures for Feature and Foundation modules.</li></ul><p>A whole different approach will be to investigate the experimental <a href=\"https://github.com/elkdanger/vs-net-dte\" rel=\"noopener\"><code>vs-net-dte</code></a> and <a href=\"https://github.com/elkdanger/gulp-notify-dte\" rel=\"noopener\"><code>gulp-notify-dte</code></a> projects in order to get DTE to work with Gulp. Please do let me know if you have plans to dig into this 😃.</p>",
      "date_published": "2016-12-28T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/blog-upgrade-excerpts-and-improving-performance/",
      "url": "https://marcduiker.dev/articles/blog-upgrade-excerpts-and-improving-performance/",
      "title": "Blog upgrade: Post excerpts, microformats and improving performance",
      "content_html": "<h2 id=\"time-for-an-upgrade\"><a href=\"#time-for-an-upgrade\" class=\"heading-anchor\">Time for an upgrade</a></h2><p>I finally made some time available to work on this blog. There were a couple of things I wanted to improve since <a href=\"/articles/moving-my-blog-i-love-github-and-markdown\">I moved to GitHub Pages/Jekyll</a>.</p><ol class=\"list\"><li>Better looking index page. Before todays change it was a <strong>very</strong> simple (and boring) bulleted list of links containing the date and title of individual blogposts.</li><li>Improved content structure. Having a well defined content structure improves SEO.</li><li>Higher performance. Faster page loads makes both users and Google happy.</li></ol><h2 id=\"index-page-with-blog-excerpts\"><a href=\"#index-page-with-blog-excerpts\" class=\"heading-anchor\">Index page with blog excerpts</a></h2><p>I wanted more body in the index page but I didn’t want a huge page with full blog posts.<br>Luckily Jekyll supports the usage of <a href=\"https://jekyllrb.com/docs/posts/#post-excerpts\" rel=\"noopener\">post excerpts</a> out of the box. You add the excerpt seperator to the _<em>config.yml</em>:</p><p><code>excerpt_separator: &lt;!--more--&gt;</code></p><p>And in the content of the post where the excerpt ends you place the seperator:</p><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token title important\"><span class=\"token punctuation\">##</span> Title</span>\n\nThis is included in the excerpt.\n\n<span class=\"token comment\">&lt;!--more--&gt;</span>\n\nThis is not included int the excerpt.\n</code></pre><p>Jekyll will use the content before the seperator as the excerpt. This can be used in the index page when iterating the posts as:</p><pre class=\"language-markdown\"><code class=\"language-markdown\">\n{% for post in paginator.posts %}\n  ...\n  {{ post.excerpt }}\n  ...\n{% endfor %}\n</code></pre><p>Super easy. Let’s continue with structuring the content.</p><h2 id=\"blogposting-microformat\"><a href=\"#blogposting-microformat\" class=\"heading-anchor\">BlogPosting microformat</a></h2><p>I stumbled on <a href=\"http://greyfocus.com/2015/05/schema.org-microformat-jekyll/\" rel=\"noopener\">this page</a> on how to use a blogpost microformat with Jekyll.</p><p>Although I heard of microformats before I never really took the time to look into it. It appears that there are microformats for <a href=\"https://schema.org/docs/full.html\" rel=\"noopener\">nearly everything</a>!<br>Ranging from the most basic <a href=\"https://schema.org/Thing\" rel=\"noopener\"><code>Thing</code></a> to <a href=\"https://schema.org/AdultEntertainment\" rel=\"noopener\"><code>AdultEntertainment</code></a> to <a href=\"https://schema.org/Volcano\" rel=\"noopener\"><code>Volcano</code></a> (notice the <code>smokingAllowed</code> propery on that one 😉 ).</p><p>The most useful for blog writers is the <a href=\"https://schema.org/BlogPosting\" rel=\"noopener\"><code>BlogPosting</code></a> microformat. I’m currently using the following properties in the <code>post.html</code> layout:</p><ul class=\"list\"><li><code>itemscope itemtype=\"http://schema.org/BlogPosting\"</code> on the <code>article</code> element.</li><li><code>itemprop=\"name\"</code> on the <code>h1</code> which contains the blog title.</li><li><code>itemprop=\"datePublished\"</code> on the <code>time</code> element.</li><li><code>itemprop=\"auhor\"</code> and <code>itemscope itemtype=\"http://schema.org/Person\"</code> on the <code>span</code> which contains my name.</li><li><code>itemprop=\"articleBody\"</code> on the <code>div</code> that contains the post body content.</li></ul><p>Google provides <a href=\"https://search.google.com/structured-data/testing-tool\" rel=\"noopener\">an online testing tool</a> to verify structured website data.<br>Currently I’m still missing <em>publisher</em> and <em>image</em> data but search engines should already be able to index my blog posts better which will improve SEO.</p><h2 id=\"improving-performance\"><a href=\"#improving-performance\" class=\"heading-anchor\">Improving performance</a></h2><p>The last thing on my list was to improve the performance of my blog. <a href=\"http://yslow.org/\" rel=\"noopener\">YSlow</a> and <a href=\"https://developers.google.com/speed/pagespeed/insights/\" rel=\"noopener\">Google PageSpeed</a> scores were around 80 out of 100 which is not bad to begin with.<br>But both of these tools indicated that using a <a href=\"https://en.wikipedia.org/wiki/Content_delivery_network\" rel=\"noopener\">content delivery network</a> (CDN) would improve performance. I’ve used CDN solutions for work projects but never considered it for personal projects like this blog.</p><p>I found that <a href=\"https://www.cloudflare.com/plans/\" rel=\"noopener\">CloudFlare</a> offers a free plan for personal websites so I signed up right away. I was quite surprised that this free plan still allows a great deal of configuration. A useful feature is the <em>developer mode</em> which disables the caching temporarily so you can see your changes quickly.</p><p>The YSlow score for this page is now at 92 and Google PageSpeed is at 85 and I’m quite content with those numbers. Now it’s time to write more posts again and get the traffic going!</p>",
      "date_published": "2016-12-03T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/improving-unit-test-readability-named-args/",
      "url": "https://marcduiker.dev/articles/improving-unit-test-readability-named-args/",
      "title": "Improving unit test readability: helper methods &amp; named arguments",
      "content_html": "<p>‘Uncle Bob’ wrote the following in <a rel=\"nofollow\" href=\"https://www.amazon.co.uk/gp/product/0132350882/\">Clean Code: A Handbook of Agile Software Craftsmanship</a>:</p><p><em>“The ratio of time spent reading (code) versus writing is well over 10 to 1 … (therefore) making it easy to read makes it easier to write.”</em></p><p>So think about that when you write your next bit of code. You need to make sure your code is easily readable and understandable for <em>others</em>, you hardly ever write code just for yourself.</p><h2 id=\"readable-unit-tests\"><a href=\"#readable-unit-tests\" class=\"heading-anchor\">Readable unit tests</a></h2><p>Unit test code is not different than ‘production’ code. Readability is key here because unclear unit tests will be distrusted, ignored and possibly removed.</p><h3 id=\"bulky-arrange-section\"><a href=\"#bulky-arrange-section\" class=\"heading-anchor\">Bulky Arrange section</a></h3><p>Consider the following unit test for testing the <code>GetHighestRatedMovies</code> method in an imaginary <code>MovieService</code>:</p><p><strong>UnitTestWithBulkyArrangeSection.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">Fact</span></span><span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">GetHighestRatedMovies_RepositoryContainsMoviesWithRatings_ReturnsMoviesOrderedByDescendingRating</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// Arrange</span>\n    <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> fixture <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Fixture</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> movieCollection <span class=\"token operator\">=</span> fixture<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">CreateMany</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Movie<span class=\"token punctuation\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token number\">20</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">ToList</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> fakeMovieRepository <span class=\"token operator\">=</span> A<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">Fake</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>IMovieRepository<span class=\"token punctuation\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    A<span class=\"token punctuation\">.</span><span class=\"token function\">CallTo</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> fakeMovieRepository<span class=\"token punctuation\">.</span><span class=\"token function\">GetAll</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Returns</span><span class=\"token punctuation\">(</span>movieCollection<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token class-name\">IMovieService</span> movieService <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">MovieService</span><span class=\"token punctuation\">(</span>fakeMovieRepository<span class=\"token punctuation\">,</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token class-name\">MovieServiceRequest</span> request <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">MovieServiceRequest</span> <span class=\"token punctuation\">{</span> NumberOfMoviesToReturn <span class=\"token operator\">=</span> <span class=\"token number\">5</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Act</span>\n    <span class=\"token class-name\">IList<span class=\"token punctuation\">&lt;</span>Movie<span class=\"token punctuation\">&gt;</span></span> movies <span class=\"token operator\">=</span> movieService<span class=\"token punctuation\">.</span><span class=\"token function\">GetHighestRatedMovies</span><span class=\"token punctuation\">(</span>request<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Assert</span>\n    movies<span class=\"token punctuation\">.</span><span class=\"token function\">Should</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">BeInDescendingOrder</span><span class=\"token punctuation\">(</span>movie <span class=\"token operator\">=&gt;</span> movie<span class=\"token punctuation\">.</span>Rating<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>Although this unit test uses some great frameworks such as <a href=\"https://xunit.github.io/\" rel=\"noopener\">xUnit</a>, <a href=\"https://github.com/AutoFixture/AutoFixture\" rel=\"noopener\">AutoFixture</a>, <a href=\"https://github.com/FakeItEasy/FakeItEasy\" rel=\"noopener\">FakeItEasy</a> and <a href=\"http://www.fluentassertions.com/\" rel=\"noopener\">FluentAssertions</a> (big fan of these!) there are still some things I don’t like, particularly in the <em>Arrange</em> section:</p><ul class=\"list\"><li>In lines 5-6 a collection of <code>Movie</code> objects is set up using <em>AutoFixture</em>. I get why this collection is neccesary but I really don’t care about <strong>how</strong> it’s done. In addition you could argue that 20 is a magic number although the intent is quite clear here. It can definitely be a bit more clear.</li><li>In lines 7-8 a fake object based on <code>IMovieRepository</code> is created using <em>FakeItEasy</em>. The fake repository is required to be able to return <code>Movie</code> objects from the <code>MovieService</code> but again I don’t really care <strong>how</strong> that is done.</li><li>In line 9 the <code>MovieService</code> is instantiated and the fake repository is passed in the constructor. But what are the <code>null</code> arguments there? What do they represent?</li><li>In line 10 a <code>MovieServiceRequest</code> object is constructed. It’s a simple value object with just one property. But what will happen if more properties are added later? Then the construction of this request will take up quite some space which has a negative impact on readability.</li></ul><p>In general I feel there is too much detail in this <em>Arrange</em> section which is not relevant for understanding the unit test.<br>Although 6 lines is not that much I do believe fewer lines in the <em>Arrange</em> and clear usage of arguments will improve the readability a lot.</p><h3 id=\"lean-arrange-section\"><a href=\"#lean-arrange-section\" class=\"heading-anchor\">Lean Arrange section</a></h3><p>Here’s how I refactored the unit test:</p><p><strong>UnitTestWithLeanArrangeSection.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">Fact</span></span><span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">GetHighestRatedMovies_RepositoryContainsMoviesWithRatings_ReturnsMoviesOrderedByDescendingRating</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// Arrange</span>\n    <span class=\"token class-name\">IMovieRepository</span> fakeMovieRepository <span class=\"token operator\">=</span> <span class=\"token function\">GetFakeMovieRepository</span><span class=\"token punctuation\">(</span><span class=\"token named-parameter punctuation\">nrOfMoviesInRepository</span><span class=\"token punctuation\">:</span> <span class=\"token number\">20</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token class-name\">IMovieService</span> movieService <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">MovieService</span><span class=\"token punctuation\">(</span>fakeMovieRepository<span class=\"token punctuation\">,</span> <span class=\"token named-parameter punctuation\">context</span><span class=\"token punctuation\">:</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span> <span class=\"token named-parameter punctuation\">logger</span><span class=\"token punctuation\">:</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token class-name\">MovieServiceRequest</span> request <span class=\"token operator\">=</span> <span class=\"token function\">GetMovieServiceRequest</span><span class=\"token punctuation\">(</span><span class=\"token named-parameter punctuation\">nrOfMoviesToReturn</span><span class=\"token punctuation\">:</span> <span class=\"token number\">5</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Act</span>\n    <span class=\"token class-name\">IList<span class=\"token punctuation\">&lt;</span>Movie<span class=\"token punctuation\">&gt;</span></span> movies <span class=\"token operator\">=</span> movieService<span class=\"token punctuation\">.</span><span class=\"token function\">GetHighestRatedMovies</span><span class=\"token punctuation\">(</span>request<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Assert</span>\n    movies<span class=\"token punctuation\">.</span><span class=\"token function\">Should</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">BeInDescendingOrder</span><span class=\"token punctuation\">(</span>movie <span class=\"token operator\">=&gt;</span> movie<span class=\"token punctuation\">.</span>Rating<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">IMovieRepository</span> <span class=\"token function\">GetFakeMovieRepository</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">int</span></span> nrOfMoviesInRepository<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> fixture <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Fixture</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> movies <span class=\"token operator\">=</span> fixture<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">CreateMany</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Movie<span class=\"token punctuation\">&gt;</span></span></span><span class=\"token punctuation\">(</span>nrOfMoviesInRepository<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">ToList</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> fakeRepository <span class=\"token operator\">=</span> A<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">Fake</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>IMovieRepository<span class=\"token punctuation\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    A<span class=\"token punctuation\">.</span><span class=\"token function\">CallTo</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> fakeRepository<span class=\"token punctuation\">.</span><span class=\"token function\">GetAll</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Returns</span><span class=\"token punctuation\">(</span>movies<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">return</span> fakeRepository<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">MovieServiceRequest</span> <span class=\"token function\">GetMovieServiceRequest</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">int</span></span> nrOfMoviesToReturn<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">MovieServiceRequest</span>\n        <span class=\"token punctuation\">{</span>\n            NumberOfMoviesToReturn <span class=\"token operator\">=</span> nrOfMoviesToReturn\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>There are two major differences:</p><ul class=\"list\"><li>Creation of fake objects and the request object is now done in private methods. This allows re-usage of these methods in future unit tests. When the number of these helper methods grows you should consider moving them to a seperate class.</li><li><a href=\"https://msdn.microsoft.com/library/dd264739.aspx\" rel=\"noopener\">Named arguments</a> (available since C# 4.0) are used when calling the helper methods and for constructing the <code>MovieService</code>. It is now evident what the <code>null</code> values actually represent. An alternative would be to declare seperate variables for all these arguments. Although that would be very clear and descriptive I believe that would hurt readability because the <em>Arrange</em> section would get quite bulky again.</li></ul><h3 id=\"tips-for-keeping-your-unit-tests-lean-and-understandable\"><a href=\"#tips-for-keeping-your-unit-tests-lean-and-understandable\" class=\"heading-anchor\">Tips for keeping your unit tests lean and understandable</a></h3><ul class=\"list\"><li>I usually only ‘new up’ the class under test directly in the <em>Arrange</em> section, other (fake) objects are created in helper methods or classes.</li><li>Use existing and proven libraries &amp; frameworks so you don’t have to write boilerplate code and you can focus on more difficult problems.<ul class=\"list\"><li>Try to use a mocking framework (such as <a href=\"https://github.com/FakeItEasy/FakeItEasy\" rel=\"noopener\">FakeItEasy</a>) over a custom made mock/stub framework. Using a custom framework costs time in two ways: it needs to be maintained and it needs to be explained to every new member on the team.</li><li>Need to create collections of fake objecs? Consider using <a href=\"http://blog.ploeh.dk/2009/05/11/AnonymousSequencesWithAutoFixture/\" rel=\"noopener\">AutoFixture’s <code>CreateMany&lt;T&gt;</code></a>.</li><li>The <a href=\"http://www.fluentassertions.com/\" rel=\"noopener\">FluentAssertions</a> library contains dozens of useful assert methods, especially for collections.</li></ul></li><li>Always be very explicit in naming the methods and arguments to avoid unclarity. Consider using named arguments when you need to pass numbers, strings or null to methods. If you’re using code analysis tools such as Resharper you’ll get warnings that the usage of named arguments is not required most of the time. You might want to lower the severity of that message so the code analysis stays ‘green’.</li></ul><p>Happy unit testing!</p>",
      "date_published": "2016-06-01T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/workflow-management-spe-module/",
      "url": "https://marcduiker.dev/articles/workflow-management-spe-module/",
      "title": "Workflow Management SPE Module",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/17.1.workflow_management-275w.webp 275w\" sizes=\"90vw\"><img src=\"/assets/images/17.1.workflow_management-275w.webp\" width=\"275\" height=\"79\" alt=\"Workflow Management SPE Module\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"sitecore-powershell-extensions-first-encounter\"><a href=\"#sitecore-powershell-extensions-first-encounter\" class=\"heading-anchor\">Sitecore PowerShell Extensions first encounter</a></h2><p>Although I had heard of <a href=\"https://marketplace.sitecore.net/Modules/S/Sitecore_PowerShell_console.aspx\" rel=\"noopener\">Sitecore PowerShell Extensions (SPE)</a> several years ago I never used it myself until earlier this month.<br>My interest was raised again during the <em>Accelerated development with Sitecore PowerShell Extensions</em> session by <a href=\"https://twitter.com/adamnaj\" rel=\"noopener\">Adam Najmanowicz</a> during SUGCON Europe 2016.</p><p>Right after I got back from SUGCON the project I was working on had a nice challenge. Workflows were being set on existing templates and hundreds of content items had already been created using these templates and had no workflow or workflow state set on them.<br>We needed a way to update the content items with the workflow that was set on the <em>Default workflow</em> field on the updated templates.</p><p>For me this was an excellent opportunity to try SPE. I wrote a Sitecore PowerShell script to update <em>Workflow</em> and <em>Workflow state</em> fields, posted it <a href=\"https://gist.github.com/marcduiker/950e0358bb4752ed5b047931a8c958c1\" rel=\"noopener\">in a gist</a> and <a href=\"https://twitter.com/marcduiker/status/728375187431936000\" rel=\"noopener\">tweeted</a> about it. I got in contact with <a href=\"https://twitter.com/michaelwest101\" rel=\"noopener\">Michael West</a> and Adam Najmanowicz (the developers of SPE) who made some excellent suggestions to improve the user friendliness of the script.</p><h2 id=\"workflow-management-module\"><a href=\"#workflow-management-module\" class=\"heading-anchor\">Workflow Management Module</a></h2><p>Adam asked if I could turn the script into a custom SPE module that could be installed alongside SPE. I really liked this idea and this made me delve deeper in SPE which is btw <a href=\"https://sitecorepowershell.gitbooks.io/sitecore-powershell-extensions/content/\" rel=\"noopener\">extremely well documented</a>.</p><p>And now the Workflow Management module is ready. Currently only containing one toolbox action: <em>Update workflow and state for content items</em> but more will follow.</p><h3 id=\"installation\"><a href=\"#installation\" class=\"heading-anchor\">Installation</a></h3><p>You’ll need SPE v4 and Sitecore 8.x to make use of the module. The zip package can be found in the <a href=\"https://github.com/marcduiker/SPE-Modules/blob/master/sitecore-packages/Workflow%20Management%20SPE%20Module-1.0.zip\" rel=\"noopener\">GitHub repo</a> or on the Sitecore Marketplace (soon).</p><p>Install the zip package using the Installation Wizard:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/17.2.workflow_management_install1-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/17.2.workflow_management_install1-440w.webp\" width=\"440\" height=\"440\" alt=\"Workflow Management SPE Module\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/17.3.workflow_management_install2-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/17.3.workflow_management_install2-440w.webp\" width=\"440\" height=\"440\" alt=\"Workflow Management SPE Module\" loading=\"lazy\" decoding=\"async\"></picture></p><h3 id=\"workflow-management-toolbox\"><a href=\"#workflow-management-toolbox\" class=\"heading-anchor\">Workflow Management toolbox</a></h3><p>Once the package is installed you’ll see that a <em>Workflow Management</em> element is added to the PowerShell toolbox:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/17.4.workflow_management_toolbox-440w.webp 440w, /assets/images/17.4.workflow_management_toolbox-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/17.4.workflow_management_toolbox-650w.webp\" width=\"650\" height=\"231\" alt=\"Workflow Management SPE Module\" loading=\"lazy\" decoding=\"async\"></picture></p><p>When you click the <em>Update workflow and state for content items</em> action you’ll be presented with the following dialog:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/17.5.workflow_management_dialog1-440w.webp 440w, /assets/images/17.5.workflow_management_dialog1-600w.webp 600w\" sizes=\"90vw\"><img src=\"/assets/images/17.5.workflow_management_dialog1-600w.webp\" width=\"600\" height=\"436\" alt=\"Workflow Management SPE Module\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Here you need to select the <strong>workflow state</strong> which is expected to be set on the content items in a later stage.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/17.6.workflow_management_dialog2-440w.webp 440w, /assets/images/17.6.workflow_management_dialog2-600w.webp 600w\" sizes=\"90vw\"><img src=\"/assets/images/17.6.workflow_management_dialog2-600w.webp\" width=\"600\" height=\"436\" alt=\"Workflow Management SPE Module\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Once you click <em>Proceed</em> the script will do the following:</p><ul class=\"list\"><li>Find the workflow for the selected workflow state.</li><li>Find the templates which have the workflow set on the <em>Default workflow</em> field in their __<em>Standard Values</em>.</li><li>Find the content items based on these templates and only list those items which <em>Workflow</em> field are empty.</li></ul><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/17.7.workflow_management_report1-440w.webp 440w, /assets/images/17.7.workflow_management_report1-650w.webp 650w, /assets/images/17.7.workflow_management_report1-960w.webp 960w, /assets/images/17.7.workflow_management_report1-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/17.7.workflow_management_report1-1200w.webp\" width=\"1200\" height=\"565\" alt=\"Workflow Management SPE Module\" loading=\"lazy\" decoding=\"async\"></picture></p><p>To update the content items with the workflow and workflow state you can choose between two actions in the menu:</p><ul class=\"list\"><li>Update workflow for all items.</li><li>Update workflow for selected items.</li></ul><p>After running one of these actions you will see a notification about the number of items that have been processed.<br>The list will be updated and now includes the <em>workflow ID</em> and <em>workflow state ID</em> values for the items.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/17.8.workflow_management_report2-440w.webp 440w, /assets/images/17.8.workflow_management_report2-650w.webp 650w, /assets/images/17.8.workflow_management_report2-960w.webp 960w, /assets/images/17.8.workflow_management_report2-1200w.webp 1200w\" sizes=\"90vw\"><img src=\"/assets/images/17.8.workflow_management_report2-1200w.webp\" width=\"1200\" height=\"565\" alt=\"Workflow Management SPE Module\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"final-thoughts\"><a href=\"#final-thoughts\" class=\"heading-anchor\">Final Thoughts</a></h2><p>This was mostly a fun learning exercise for me getting to know Sitecore PowerShell Extensions. I really like SPE and I plan to use it much more for automating tedious manual tasks.</p><p>I do hope this Workflow Management module can be of use to others. Please make sure you try it on a development or test environment first before using it on production.</p><p>When I tested it on 101 content items it took about 10 seconds to process them on my (outdated) local machine. So be careful when you want to process large amounts of items.<br>In that case you might be better of with the <a href=\"https://gist.github.com/marcduiker/950e0358bb4752ed5b047931a8c958c1\" rel=\"noopener\">simple script</a> without the UI.</p><p>If you have any feature requests or issues, please post them on <a href=\"https://github.com/marcduiker/SPE-Modules\" rel=\"noopener\">GitHub</a>.</p>",
      "date_published": "2016-05-16T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/sitecore-investigation-error-installing-item-bucket-package/",
      "url": "https://marcduiker.dev/articles/sitecore-investigation-error-installing-item-bucket-package/",
      "title": "Sitecore investigation: Errors installing a content package with item buckets",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/16.1.bucket_icon-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/16.1.bucket_icon-440w.webp\" width=\"440\" height=\"186\" alt=\"Bucket Icon\" loading=\"lazy\" decoding=\"async\"></picture></p><p>A colleague showed me this error today in the Sitecore log:</p><p><code>ERROR There is no appropriate index for [item path - {GUID}]. You have to add an index crawler that will cover this item</code></p><p><em>‘I’ve never seen that!’</em> was my first reaction…</p><p>This issue occurred when a Sitecore zip package with content items was installed on another environment. There were quite some lines in the Sitecore log which mentioned the same error but for different item paths and GUIDS. I looked up the GUIDS on my local machine and they were all <code>Bucket</code> folder items with no content items in them.</p><p>Here’s an example of how that looks like (when you’ve checked the <em>Buckets</em> option in the <em>View</em> toolbar):</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/16.2.item_bucket_without_content-268w.webp 268w\" sizes=\"90vw\"><img src=\"/assets/images/16.2.item_bucket_without_content-268w.webp\" width=\"268\" height=\"288\" alt=\"Item bucket with an empty bucket folder\" loading=\"lazy\" decoding=\"async\"></picture></p><h3 id=\"cause\"><a href=\"#cause\" class=\"heading-anchor\">Cause</a></h3><p>Apparently when <em>bucketable</em> items are deleted their automatically generated parent folders are not deleted. There is a built-in task that periodically removes the empty <code>Bucket</code> items but this is disabled by default in the configuration as Raúl Jiménez describes in <a href=\"http://blog.rauljimenez.co.uk/the-depths-of-the-bucket/\" rel=\"noopener\">this post</a>.</p><h3 id=\"solution\"><a href=\"#solution\" class=\"heading-anchor\">Solution</a></h3><p>When you use item buckets, make sure you uncomment the <code>RemoveEmptyBucketFolders</code> agent section in the Sitecore.Buckets.config as shown below (or even better: make a patch file to enable it):</p><p><strong>Sitecore.Buckets.config.xml</strong></p><pre class=\"language-xml\"><code class=\"language-xml\"><span class=\"token comment\">&lt;!-- EMPTY BUCKET CLEANING JOB\n         This job runs at the set interval time and removes item bucket folders that no longer contain any items.\n         This could be cause by deleting an item from a bucket, which has no other items in it.\n--&gt;</span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>scheduling</span><span class=\"token punctuation\">&gt;</span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>frequency</span><span class=\"token punctuation\">&gt;</span></span>00:00:05<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>frequency</span><span class=\"token punctuation\">&gt;</span></span>\n  <span class=\"token comment\">&lt;!-- Adjust the interval and frequency as needed to speed things up on a local dev environment. --&gt;</span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>agent</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Sitecore.Buckets.Tasks.RemoveEmptyBucketFolders<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">method</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Run<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">interval</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>00:00:10<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">&gt;</span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>DatabaseName</span><span class=\"token punctuation\">&gt;</span></span>master<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>DatabaseName</span><span class=\"token punctuation\">&gt;</span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>agent</span><span class=\"token punctuation\">&gt;</span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>scheduling</span><span class=\"token punctuation\">&gt;</span></span></code></pre><p>Finally, always double check if there are empty <code>Bucket</code> folder items before making a content package that will contain bucketable items.</p><p><strong>Case closed</strong></p>",
      "date_published": "2016-04-28T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/ruling-the-continuous-integration-seas-with-sitecore-ship-part-2/",
      "url": "https://marcduiker.dev/articles/ruling-the-continuous-integration-seas-with-sitecore-ship-part-2/",
      "title": "Ruling the continuous integration seas with Sitecore.Ship - Part 2: fileupload",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/15.1.one-does-not-simply-make-a-proper-http-request-440w.webp 440w, /assets/images/15.1.one-does-not-simply-make-a-proper-http-request-568w.webp 568w\" sizes=\"90vw\"><img src=\"/assets/images/15.1.one-does-not-simply-make-a-proper-http-request-568w.webp\" width=\"568\" height=\"335\" alt=\"One does not simply make a proper http request\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"deploying-sitecore-items-with-sitecoreship\"><a href=\"#deploying-sitecore-items-with-sitecoreship\" class=\"heading-anchor\">Deploying Sitecore items with Sitecore.Ship</a></h2><p>As I mentioned in the <a href=\"/articles/ruling-the-continuous-integration-seas-with-sitecore-ship-part-1\">previous post</a><br>Sitecore.Ship can be used to install Sitecore <code>update</code> or <code>zip</code> packages by posting HTTP requests to the Sitecore site.</p><h3 id=\"configuration\"><a href=\"#configuration\" class=\"heading-anchor\">Configuration</a></h3><p>I forgot to talk about the configuration in the previous post so let’s have a look at that now.<br>The Sitecore.Ship configuration is split into two parts:</p><ul class=\"list\"><li><p>The <strong><code>ship.config</code></strong> file (located in App_Config\\Include) contains the<br>patched <code>IgnoreUrlPrefixes</code> attribute to include the <code>/services/</code> url part which Sitecore.Ship is using.</p></li><li><p>The <strong><code>web.config</code></strong> is updated with a <code>packageInstallation</code> element.<br>The default values of this element are:</p><p><code>&lt;packageInstallation enabled=\"true\" allowRemote=\"false\" allowPackageStreaming=\"false\" recordInstallationHistory=\"false\" /&gt;</code></p><p>The attributes are pretty self explanatory. I’ll get to the <code>recordInstallationHistory</code> in a later post.<br>Just make sure it is <code>false</code> otherwise there will be errors about a missing <em>PackageId</em>.</p></li></ul><h3 id=\"uploading-and-installing-a-package\"><a href=\"#uploading-and-installing-a-package\" class=\"heading-anchor\">Uploading and installing a package</a></h3><p>One of the most useful commands of Sitecore.Ship is <code>fileupload</code>. When you issue an HTTP request to <code>&lt;website&gt;/services/package/install/fileupload</code><br>you can upload <em>and</em> install a Sitecore package.</p><p>The <a href=\"https://github.com/kevinobee/Sitecore.Ship/wiki/Package-Install-Upload\" rel=\"noopener\">wiki</a> describes that you need to provide the path of<br>the package as form-data in the request. Lets have look how that is done exactly.</p><h4 id=\"postman\"><a href=\"#postman\" class=\"heading-anchor\">Postman</a></h4><p>The easiest way to test the commands Sitecore.Ship offers is to use an HTTP/REST client such as <a href=\"https://www.getpostman.com/\" rel=\"noopener\">Postman</a>,<br>which I’m using here.</p><ul class=\"list\"><li><p>Once you’ve started Postman you’ll see this screen:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/15.2.postman_start-440w.webp 440w, /assets/images/15.2.postman_start-650w.webp 650w, /assets/images/15.2.postman_start-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/15.2.postman_start-960w.webp\" width=\"960\" height=\"365\" alt=\"Postman startup screen\" loading=\"lazy\" decoding=\"async\"></picture></p></li><li><p>Change the following fields to do a post request to upload and install a package:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/15.3.postman_data-440w.webp 440w, /assets/images/15.3.postman_data-650w.webp 650w, /assets/images/15.3.postman_data-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/15.3.postman_data-960w.webp\" width=\"960\" height=\"365\" alt=\"Postman fileupload post request\" loading=\"lazy\" decoding=\"async\"></picture></p></li><li><p>Note that the value of the <em>Key</em> parameter (<code>path</code> in this example) is actually irrelevant, it can be any value.</p></li><li><p>Once the value type is set to <code>File</code> an <em>Open file</em> dialog can be used to select the file to upload.</p></li><li><p>Now press the blue Send button to do the post request.<br>If everything went well output shows the Sitecore IDs and path of the items that were in the package and have been installed:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/15.4.postman_result-440w.webp 440w, /assets/images/15.4.postman_result-650w.webp 650w, /assets/images/15.4.postman_result-960w.webp 960w\" sizes=\"90vw\"><img src=\"/assets/images/15.4.postman_result-960w.webp\" width=\"960\" height=\"658\" alt=\"Postman fileupload response\" loading=\"lazy\" decoding=\"async\"></picture></p></li></ul><p>So far so good, but we don’t want to use Postman manually in order to upload and install packages for every deployment right?<br>We need a solution that can be automated and used in a continuous integration setup.</p><h4 id=\"curl\"><a href=\"#curl\" class=\"heading-anchor\">cURL</a></h4><p>I first looked into PowerShell and the <code>Invoke-RestMethod</code> command but it appeared that OOTB this method does not support multipart form data, which is required to call the <code>fileupload</code> command.<br>There is <a href=\"http://stackoverflow.com/a/25083745/112544\" rel=\"noopener\">a workaround</a> to create the required multipart boundaries in the request but I did not like this approach. I looked for another solution and found cURL.</p><p><a href=\"http://curl.haxx.se/\" rel=\"noopener\">cURL</a> is a very powerful commandline application to script HTTP jobs.<br>Getting the syntax right can be a little tricky although there is quite some <a href=\"http://curl.haxx.se/docs/httpscripting.html\" rel=\"noopener\">documentation</a>.<br>Luckily Postman can generate various scripts including one for cURL:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/15.5.postman_generate-440w.webp 440w, /assets/images/15.5.postman_generate-650w.webp 650w, /assets/images/15.5.postman_generate-888w.webp 888w\" sizes=\"90vw\"><img src=\"/assets/images/15.5.postman_generate-888w.webp\" width=\"888\" height=\"293\" alt=\"Postman generate code\" loading=\"lazy\" decoding=\"async\"></picture></p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/15.6.postman_curl-440w.webp 440w, /assets/images/15.6.postman_curl-559w.webp 559w\" sizes=\"90vw\"><img src=\"/assets/images/15.6.postman_curl-559w.webp\" width=\"559\" height=\"414\" alt=\"Postman cURL code\" loading=\"lazy\" decoding=\"async\"></picture></p><p>However the cURL script in the screenshot above contains a lot of unncessesary statements and actually gives errors.<br>I’ve found that this is the minimal cURL syntax which works for me:</p><p><code>curl -F \"path=@&lt;path to update or zip package&gt;\" 'http://&lt;website&gt;/services/package/install/fileupload'</code></p><p>Sofar we’ve only replaced Postman with cURL, but since cURL is a commandline tool it can be easily called<br>from a script during a deployment process as we’ll see next.</p><h4 id=\"powershell\"><a href=\"#powershell\" class=\"heading-anchor\">PowerShell</a></h4><p>In the Gist below you can see the <em>deploy-sitecorepackage.ps1</em> script which I use to upload and deploy Sitecore packages.<br>I actually prefer to use the more verbose cURL syntax (e.g. <code>--form</code> instead of <code>-F</code>) because I believe the intention<br>of the script is much more clear to the reader who might not know the syntax well.<br>A full description of the parameters can be found in the <a href=\"http://curl.haxx.se/docs/manpage.html\" rel=\"noopener\">cURL manual</a>.</p><p><strong>deploy-sitecorepackage.ps1</strong></p><pre class=\"language-powershell\"><code class=\"language-powershell\"><span class=\"token comment\">&lt;#\n    This function uploads &amp; installs the specified Sitecore update package to the given $SiteUrl.\n    It uses cURL (http://curl.haxx.se/) to post a request to a Sitecore website which has Sitecore Ship installed.\n\n    Example usage: \n    .\\deploy-sitecorepackage.ps1 mysite.dev \"C:\\Project\\Build\\Artifacts\\1-mysite-templates.update\" 60 300\n#&gt;</span>\n\n<span class=\"token keyword\">Param</span><span class=\"token punctuation\">(</span>\n    <span class=\"token namespace\">[Parameter(Position=0, Mandatory=$true)]</span>\n    <span class=\"token namespace\">[string]</span><span class=\"token variable\">$SiteUrl</span><span class=\"token punctuation\">,</span>\n    <span class=\"token namespace\">[Parameter(Position=1, Mandatory=$true)]</span>\n    <span class=\"token namespace\">[string]</span><span class=\"token variable\">$UpdatePackagePath</span><span class=\"token punctuation\">,</span>\n    <span class=\"token namespace\">[Parameter(Position=2)]</span>\n    <span class=\"token namespace\">[ValidateRange(0, 99999)]</span>\n    <span class=\"token namespace\">[int]</span><span class=\"token variable\">$ConnectionTimeOutInSeconds</span> = 300<span class=\"token punctuation\">,</span>\n    <span class=\"token namespace\">[Parameter(Position=3)]</span>\n    <span class=\"token namespace\">[ValidateRange(0, 99999)]</span>\n    <span class=\"token namespace\">[int]</span><span class=\"token variable\">$MaxTimeOutInSeconds</span> = 900\n<span class=\"token punctuation\">)</span>\n\n<span class=\"token variable\">$fileUploadUrl</span> = <span class=\"token string\">\"<span class=\"token variable\">$SiteUrl</span>/services/package/install/fileupload\"</span>\n<span class=\"token variable\">$curlPath</span> = <span class=\"token punctuation\">.</span>\\<span class=\"token function\">get-curlpath</span><span class=\"token punctuation\">.</span>ps1\n<span class=\"token variable\">$curlCommand</span>= <span class=\"token string\">\"<span class=\"token variable\">$curlPath</span> --show-error --silent --connect-timeout <span class=\"token variable\">$ConnectionTimeOutInSeconds</span> --max-time <span class=\"token variable\">$MaxTimeOutInSeconds</span> --form \"</span><span class=\"token string\">\"filename=@<span class=\"token variable\">$UpdatePackagePath</span>\"</span><span class=\"token string\">\" <span class=\"token variable\">$fileUploadUrl</span>\"</span>\n\n<span class=\"token function\">Write-Output</span> <span class=\"token string\">\"INFO: Starting Invoke-Expression: <span class=\"token variable\">$curlCommand</span>\"</span>\n\n<span class=\"token function\">Invoke-Expression</span> <span class=\"token variable\">$curlCommand</span></code></pre><p>The <em>deploy-sitecorepackage.ps1</em> script uses another script called <em>get-curlpath.ps1</em> to obtain the path to the cURL executable.</p><p><strong>get-curlpath.ps1</strong></p><pre class=\"language-powershell\"><code class=\"language-powershell\"><span class=\"token comment\">&lt;# \n    This script returns the full path of the curl.exe.\n#&gt;</span>\n\n<span class=\"token variable\">$curlExe</span> = <span class=\"token string\">'curl.exe'</span>\n<span class=\"token variable\">$curlPath</span> = <span class=\"token function\">Resolve-Path</span> <span class=\"token string\">\"<span class=\"token variable\">$PSScriptRoot</span>\\..\\tools\\curl-7.33.0-win64-nossl\\<span class=\"token variable\">$curlExe</span>\"</span> <span class=\"token comment\"># This is the path on the local dev machine.</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">-not</span> <span class=\"token punctuation\">(</span><span class=\"token function\">Test-Path</span> <span class=\"token variable\">$curlPath</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token comment\"># Fall-back to use curl.exe located in the same location as the script.</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">Test-Path</span> <span class=\"token string\">\"<span class=\"token variable\">$PSScriptRoot</span>\\<span class=\"token variable\">$curlExe</span>\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token variable\">$curlPath</span> = <span class=\"token string\">\"<span class=\"token variable\">$PSScriptRoot</span>\\<span class=\"token variable\">$curlExe</span>\"</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">else</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token function\">Write-Error</span> <span class=\"token string\">\"ERROR: <span class=\"token variable\">$curlPath</span> not found.\"</span>\n        <span class=\"token keyword\">Exit</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token variable\">$curlPath</span></code></pre><p>These PowerShell scripts can now easily be used in continuous integration &amp; delivery tools such as Octopus Deploy or Microsoft Release Management.</p><h3 id=\"whats-next\"><a href=\"#whats-next\" class=\"heading-anchor\">What’s next?</a></h3><p>In the next post I’ll explain how Sitecore.Ship can be used to record the package installation history.</p>",
      "date_published": "2015-10-31T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/ruling-the-continuous-integration-seas-with-sitecore-ship-part-1/",
      "url": "https://marcduiker.dev/articles/ruling-the-continuous-integration-seas-with-sitecore-ship-part-1/",
      "title": "Ruling the continuous integration seas with Sitecore.Ship - Part 1",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/14.1.picard_packages-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/14.1.picard_packages-440w.webp\" width=\"440\" height=\"308\" alt=\"Why the hell are you deploying Sitecore packages manually?\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"deploying-sitecore-items\"><a href=\"#deploying-sitecore-items\" class=\"heading-anchor\">Deploying Sitecore items</a></h2><p>All software development projects that involve Sitecore are faced with the same problem:<br>how to get the Sitecore items (templates, layouts or content) from development to the<br>other environments in a consistent way.</p><h3 id=\"tedious-and-error-prone-manual-deployment-o_o\"><a href=\"#tedious-and-error-prone-manual-deployment-o_o\" class=\"heading-anchor\">Tedious and error prone: manual deployment o_O</a></h3><p>During my first couple of Sitecore projects the packaging and deployment of Sitecore items<br>was a manual <s>struggle</s> process. Item packages were created using the <em>Package Designer</em> and the resulting packages<br>were installed using the <em>Installation Wizard</em> once the website was deployed.</p><p>There are several downsides to this manual approach:</p><ul class=\"list\"><li>Developers can easily forget to include required items in the package (especially if they pick items<br>the static way instead of the dynamic way).</li><li>Correct package installation is never guaranteed to happen in the same way across all<br>environments (or even at all!) because a developer needs to:<ol class=\"list\"><li>Wait until the regular deployment of files is finished (if this takes long the next steps might be forgotten),</li><li>Upload the <em>right</em> packages,</li><li>Install the packages in the <em>right</em> order (<em>and</em> select the <em>correct</em> installation options).</li></ol></li></ul><p>This manual process is very tedious and error prone, even when it’s done by just one person. Fortunately several tools emerged over the years which make packaging and deploying Sitecore items a breeze.</p><h3 id=\"developer-friendly-and-consistent-sitecore-ship\"><a href=\"#developer-friendly-and-consistent-sitecore-ship\" class=\"heading-anchor\">Developer friendly and consistent: Sitecore Ship</a></h3><p>One of the tools that made my life a Sitecore developer easier is called <a href=\"https://github.com/kevinobee/Sitecore.Ship\" rel=\"noopener\">Sitecore.Ship</a>.<br>Sitecore.Ship is a small Sitecore module developed by <a href=\"https://twitter.com/kevinobee\" rel=\"noopener\">Kevin Obee</a> and it allows the installation<br>of Sitecore packages (both <code>.zip</code> and <code>.update</code>) using HTTP requests. The packages could be either generated by <a href=\"http://teamdevelopmentforsitecore.com/\" rel=\"noopener\">TDS</a>, <a href=\"https://github.com/adoprog/Sitecore-Courier\" rel=\"noopener\">Courier</a> or… still by hand (but you wouldn’t do that anymore right? 😉.</p><p>I like Sitecore.Ship for two reasons:</p><ul class=\"list\"><li>It’s a compact module which only does a limited number of things and it does these right.<br>I usually prefer smaller tools over larger ones because smaller tools are easier to understand and work with.<br>And if there are less moving parts there is also less risk that something breaks.</li><li>It can be easily integrated in a continuous integration setup using a scripting language such as Powershell.</li></ul><h2 id=\"using-sitecoreship\"><a href=\"#using-sitecoreship\" class=\"heading-anchor\">Using Sitecore.Ship</a></h2><p>Sitecore.Ship can be added to your Visual Studio project using NuGet.<br>There are currently two NuGet packages to choose from:</p><ul class=\"list\"><li><a href=\"https://www.nuget.org/packages/Sitecore.Ship/\" rel=\"noopener\">Sitecore.Ship</a> - This version uses <em>Nancy</em>, a lightweight .Net framework for building HTTP based services. Note that your web project will add a dependency to <a href=\"https://www.nuget.org/packages/Nancy.Hosting.Aspnet/\" rel=\"noopener\">Nancy.Hosting.AspNet</a>.</li><li><a href=\"https://www.nuget.org/packages/Sitecore.Ship.AspNet/\" rel=\"noopener\">Sitecore.Ship.AspNet</a> - This version uses a plain <a href=\"http://ASP.Net\" rel=\"noopener\">ASP.Net</a> HttpHandler implementation and therefore requires no additional dependencies.</li></ul><p>Sofar I’ve mainly used the Sitecore.Ship.AspNet version because I didn’t want to introduce additional dependencies to my projects.<br>I’ve used the 0.3.5 version for Sitecore 7.1 and the <a href=\"https://www.myget.org/F/sitecore-ship-prerelease/\" rel=\"noopener\">0.4.0 (pre-release) version</a> for Sitecore 8.0 (from Initial up to Update-5).</p><h3 id=\"whats-in-the-nuget\"><a href=\"#whats-in-the-nuget\" class=\"heading-anchor\">What’s in the NuGet?</a></h3><p>Let’s have a look what’s in the NuGet packages of the 0.4.0 pre-release version (since we’re all using Sitecore 8 anyway right? 😉.</p><p>This is the contents of the Sitecore.Ship package:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/14.2.ship_nancy_nuget-440w.webp 440w, /assets/images/14.2.ship_nancy_nuget-650w.webp 650w, /assets/images/14.2.ship_nancy_nuget-928w.webp 928w\" sizes=\"90vw\"><img src=\"/assets/images/14.2.ship_nancy_nuget-928w.webp\" width=\"928\" height=\"662\" alt=\"Sitecore.Ship NuGet package\" loading=\"lazy\" decoding=\"async\"></picture></p><p>This is the contents of the Sitecore.Ship.AspNet package:</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/14.3.ship_aspnet_nuget-440w.webp 440w, /assets/images/14.3.ship_aspnet_nuget-650w.webp 650w, /assets/images/14.3.ship_aspnet_nuget-928w.webp 928w\" sizes=\"90vw\"><img src=\"/assets/images/14.3.ship_aspnet_nuget-928w.webp\" width=\"928\" height=\"701\" alt=\"Sitecore.Ship.AspNet NuGet package\" loading=\"lazy\" decoding=\"async\"></picture></p><p>The assemblies involved are:</p><ul class=\"list\"><li><code>Sitecore.Ship.Core.dll</code> - Contains the interfaces, domain objects and services for recording the package installation and parsing the packages.</li><li><code>Sitecore.Ship.Infrastructure.dll</code> - Reads the configuration, performs logging, reads/writes the package deployment history and performs the actual installation of items using the Sitecore API. This assembly has dependencies to <code>Sitecore.Kernel</code>, <code>Sitecore.Logging</code>, <code>Sitecore.Update</code> and <code>Sitecore.Zip</code>.</li><li><code>Sitecore.Ship.dll</code> - Contains Install and Publish modules (based on <code>NancyModule</code>) which define the HTTP requests that can be executed.</li><li><code>Sitecore.Ship.AspNet.dll</code> - Contains the <code>SitecoreShipHttpHandler</code> and the various CommandHandlers for uploading and installing packages and publishing items.</li></ul><p>Once the website files are deployed you can issue a POST request to <code>&lt;website&gt;/services/package/install/fileupload</code> in order to upload and install a package.<br>Or do a smart publish by doing a POST to <code>&lt;website&gt;/services/publish/smart</code>.<br>As usual though the devil in the details and while the <a href=\"https://github.com/kevinobee/Sitecore.Ship/wiki\" rel=\"noopener\">wiki page</a> gives some basic guidance how to start using Sitecore.Ship it is not a complete end-to-end guide how to integrate it in your solution.</p><p>In <a href=\"/articles/ruling-the-continuous-integration-seas-with-sitecore-ship-part-2\">the next blog post</a> I will explain in detail how I’m using Sitecore.Ship with <a href=\"https://learn.microsoft.com/powershell/\" rel=\"noopener\">Powershell</a> and <a href=\"http://curl.haxx.se/\" rel=\"noopener\">cURL</a> in continuous integration setups with tools like Visual Studio Release Management or Octopus Deploy.</p>",
      "date_published": "2015-10-21T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/choosing-communication-tools-for-distributed-teams/",
      "url": "https://marcduiker.dev/articles/choosing-communication-tools-for-distributed-teams/",
      "title": "Choosing communication tools for distributed teams",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/13.slack_home-440w.webp 440w, /assets/images/13.slack_home-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/13.slack_home-650w.webp\" width=\"650\" height=\"338\" alt=\"Slack\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"team-communication\"><a href=\"#team-communication\" class=\"heading-anchor\">Team communication</a></h2><p>My work at <a href=\"http://www.tahzoo.com\" rel=\"noopener\">Tahzoo</a> and the work I do for the <a href=\"http://www.sugnl.net\" rel=\"noopener\">Dutch Sitecore user group</a> require me to do a lot of communication in distributed teams.<br>Effective and clear communication is difficult, even more so when the team members are at different locations. Having the right tools in place helps a lot.<br>Since there are so many communication tools out there I want to share my opinion on some of these so perhaps you can make a more informed judgment when deciding which tools to use.</p><p><strong>Go straight to the <a href=\"#tldr-my-choice-of-tooling\">TLDR</a></strong></p><h3 id=\"types-of-communication\"><a href=\"#types-of-communication\" class=\"heading-anchor\">Types of communication</a></h3><p>There are various types of communication I do during the day:</p><ul class=\"list\"><li>Informing my local colleagues of a new technology meetup we should attend.</li><li>Performing a code review with one of my team members who is located in another country.</li><li>Show appreciation towards a colleague who did something great in a project.</li><li>Discuss with colleagues (world wide) how to unify our continuous integration set-ups.</li><li>Converse with fellow Sitecore user group organization members and hosts (located throughout the Netherlands) about the topics of the next user group meeting.</li></ul><p>The type of communication is either:</p><ul class=\"list\"><li>A one-off message (mostly asynchronous, such as a news update)</li><li>A conversation (either text or video)</li></ul><p>These different types and targets (individual, group, or organization) usually require different tools to support the specific communication needs. I haven’t seen one tool that supports everything well.</p><h2 id=\"the-tools\"><a href=\"#the-tools\" class=\"heading-anchor\">The tools</a></h2><p>The focus of this post is mostly on tools for (a)synchronous text messaging (conversations) and not so much video conferencing tools.</p><p><em>Please note that this is not an extensive list of communication tools out there. It’s far from that, it’s a shortlist of tools I’ve used intensively over the last years.</em></p><h3 id=\"slack\"><a href=\"#slack\" class=\"heading-anchor\">Slack</a></h3><p><a href=\"https://slack.com\" rel=\"noopener\">Slack.com</a> is definitely my favorite conversation platform nowadays. I like the minimal interface on the web and the desktop client, the ability to focus conversations around topics and the intuitive way of notifying people.<br>I haven’t even started about the fantastic <a href=\"https://slack.com/apps\" rel=\"noopener\">integrations</a> it offers with other tools.</p><p>Slack is great for synchronous conversations, it’s not meant for one-off messages.<br>With Slack you can communicate in three ways:</p><ul class=\"list\"><li>Participate in a public channel to discuss a specific topic. Anyone in the Slack team can join a channel.</li><li>Participate in a private group to discuss a specific topic. You’ll need to be invited for a group.</li><li>Send a private (direct) message to another user.</li></ul><p>What I really like about Slack is that you can easily notify other users in different ways:</p><ul class=\"list\"><li>type <code>@&lt;username&gt;</code> in a channel to notify one user in specific. The others in the group/channel can also see this message but won’t receive a notification.</li><li>type <code>@channel</code> in a channel to notify all members of that channel with your message.</li><li>type <code>@everyone</code> in a public channel to notify the entire team.</li></ul><p>Next to the web interface Slack has <a href=\"https://slack.com/downloads\" rel=\"noopener\">desktop clients</a> for Windows, Mac and Linux. Mobile apps are available for Android, iOS and Windows.</p><p><em>Am I a Slack fanboy? Oh yes 😃. I’m currently in three Slack teams which works seamlessly with the desktop client.</em></p><h3 id=\"yammer\"><a href=\"#yammer\" class=\"heading-anchor\">Yammer</a></h3><p><a href=\"http://www.yammer.com\" rel=\"noopener\">Yammer</a> promotes itself as the enterprise social network for businesses. It’s the Facebook for organizations. It handles one-off/asynchronous messages very well. It’s great for news updates about an upcoming event you want to promote or showing appreciation to a colleague. Co-workers on Yammer can like the message and reply to it but it’s not a platform for synchronous conversations. I’d say it’s quite complementary to what Slack is offering.</p><p>Next to the web interface Yammer offers a (quite limited) <a href=\"https://products.office.com/en/yammer/yammer-desktop-notifier\" rel=\"noopener\">desktop notifier</a> (Windows only) and mobile apps for <a href=\"https://play.google.com/store/apps/details?id=com.yammer.v1&amp;hl=en\" rel=\"noopener\">Android</a>, <a href=\"https://itunes.apple.com/en/app/yammer/id289559439?mt=8\" rel=\"noopener\">iOS</a> and <a href=\"https://www.microsoft.com/en-us/store/apps/yammer/9wzdncrfhwmz\" rel=\"noopener\">Windows</a>.</p><p>My two major complaints with Yammer are:</p><ul class=\"list\"><li>You <a href=\"https://community.office365.com/en-us/f/176/t/228840\" rel=\"noopener\">can’t edit a message that is posted</a>! Support advises to delete and re-post the message… Seriously Microsoft, this user experience is <em>very</em> bad.</li><li>The <a href=\"https://community.office365.com/en-us/f/176/t/246121\" rel=\"noopener\">event functionality was dropped</a> some time ago.</li></ul><h3 id=\"skype\"><a href=\"#skype\" class=\"heading-anchor\">Skype</a></h3><p><a href=\"http://www.skype.com\" rel=\"noopener\">Skype</a> has long been the preferred tool of communication for my colleagues for both chat and video. I can see why; it’s easy to use, allows you to communicate to a group and do video and screen sharing.</p><p>I would only recommend Skype when video or screen sharing is required and Google Hangouts can’t be used. I wouldn’t recommend it for chat conversations due to it’s lack of channels, less advanced notifications and lack of integrations when compared to Slack.</p><p>When I use Skype I mostly use the Windows desktop client. I’ve also used the web version of Skype for Business (because that desktop client didn’t want to install). Skype also has a <a href=\"http://www.skype.com/en/download-skype/skype-for-computer/\" rel=\"noopener\">desktop clients</a> for Mac and Linux and <a href=\"http://www.skype.com/en/download-skype/skype-for-mobile/\" rel=\"noopener\">mobile apps</a> for Android, iOS, Windows, Blackberry, Nokia X and Amazon Fire Phone.</p><h3 id=\"office365\"><a href=\"#office365\" class=\"heading-anchor\">Office365</a></h3><p>With Office365 you can create <a href=\"https://www.youtube.com/watch?v=t3OLvYXepvE\" rel=\"noopener\">public or private groups</a> and start a ‘conversation’ there. Don’t expect a fluent conversation experience such as Slack though. The conversation is actually made up of email like messages on which you can perform actions such as reply, reply all, forward and like. It’s a good fit for more official communication, capturing information and documenting everthing long term, especially when you use the other integrated functionalities such as Files, Calender and Notebook.<br>It still seems that Office365 is not 100% cross-browser compatible though. I couldn’t access a group Notebook using Chrome 😦. A colleague noticed that when you reply to a group conversation using Outlook instead of the web interface the reply starts in a new thread. Not very useful to keep track of the conversation.</p><p>The are mobile apps available for Android, iOS and Windows called <em>Outlook Groups</em> (yes, it’s <em>not</em> called <em>Office365 Groups</em>). The apps are not available globally though as is mentioned in <a href=\"http://windowsitpro.com/blog/outlook-office-365-groups-app-for-mobile-devices\" rel=\"noopener\">this post</a>.</p><h3 id=\"google-hangouts\"><a href=\"#google-hangouts\" class=\"heading-anchor\">Google Hangouts</a></h3><p>I use <a href=\"https://hangouts.google.com/\" rel=\"noopener\">Google Hangouts</a> frequently for video conferencing with other Sitecore user group members. Stability and video quality is good and screen sharing works flawlessly. If you have a Youtube channel as well you can even do live streaming. I hardly use Hangouts for chat anymore since it’s far behind compared to Slack.</p><p>Besides the web interface Hangouts is available for Android, iOS and as <a href=\"https://chrome.google.com/webstore/detail/google-hangouts/knipolnnllmklapflnccelgolnpehhpl?hl=en\" rel=\"noopener\">Chrome extension</a>.</p><h2 id=\"tldr-my-choice-of-tooling\"><a href=\"#tldr-my-choice-of-tooling\" class=\"heading-anchor\">TLDR - My choice of tooling</a></h2><p><em>There is simply not just one tool that can supports everything well. So pick the right tool for the right job.</em></p><ul class=\"list\"><li>For synchronous conversations/discussions use <a href=\"http://www.slack.com\" rel=\"noopener\">Slack</a> because the conversations are focused in channels/groups and it offers handy integrations.</li><li>For one-off/asynchronous messages, targeted to either the whole organization or to a group, use <a href=\"http://www.yammer.com\" rel=\"noopener\">Yammer</a>.</li><li>For video conferencing use Google Hangouts or Skype. I have a slight preference for Hangouts since it integrates with Slack 😃.</li></ul><h3 id=\"cant-use-a-single-tool-integrate\"><a href=\"#cant-use-a-single-tool-integrate\" class=\"heading-anchor\">Can’t use a single tool? Integrate!</a></h3><p>I recently stumbled upon <a href=\"http://sameroom.io\" rel=\"noopener\">Sameroom.io</a>. This is a platform that bridges otherwise isolated communication tools.<br>For instance: when you have distributed teams where one team is using Slack and the other is using Skype. In Sameroom you can setup a so-called <em>tube</em> and create a bridge between a Slack channel and a Skype group so both teams are in the same conversation.</p><h3 id=\"other-aspects\"><a href=\"#other-aspects\" class=\"heading-anchor\">Other aspects</a></h3><p>There are of course other things to consider when choosing communication tools. You need to think about integrating it into the existing application landscape, how to administer users, security etc. Try various tools with some stakeholders to see what is the best fit. Make an informed decision and define a plan before you roll-out to the entire organization.</p>",
      "date_published": "2015-10-14T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/moving-my-blog-i-love-github-and-markdown/",
      "url": "https://marcduiker.dev/articles/moving-my-blog-i-love-github-and-markdown/",
      "title": "Moving my blog - I ❤️ Github &amp; Markdown",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/12.successkid_blog-440w.webp 440w\" sizes=\"90vw\"><img src=\"/assets/images/12.successkid_blog-440w.webp\" width=\"440\" height=\"440\" alt=\"Moved blog to Github, writing posts in markdown.\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"the-blogging-experience\"><a href=\"#the-blogging-experience\" class=\"heading-anchor\">The blogging experience</a></h2><p>Blogging should be hassle free. With my old blog platform, blogging was far from that.</p><p>Party this was due because I deviated from the built-in themes and I hacked something custom into it. I always needed to manipulate the html of a post because the paragraphs tags and white space were off… very annoying.</p><p>And when blogging gets tedious because of such things you blog less and less. It’s been almost a year since I posted anything, which is of course unacceptable 😉.</p><h2 id=\"moved\"><a href=\"#moved\" class=\"heading-anchor\">Moved!</a></h2><p>So, it was time for desperate measures and I decided to move my blog to Github.</p><h3 id=\"host-github-pages\"><a href=\"#host-github-pages\" class=\"heading-anchor\">Host: Github Pages</a></h3><p>Github has a really great feature called <a href=\"https://pages.github.com/\" rel=\"noopener\">Github Pages</a>. Github Pages is primarily intended to create static websites for projects you have in Github repositories. It also allows a website per Github user or organisation (<code>http://&lt;username&gt;.github.io</code>). For my blog I decided to have a Github Pages <a href=\"http://marcduiker.github.io\" rel=\"noopener\">user website</a>.</p><h3 id=\"blog-engine-jekyll\"><a href=\"#blog-engine-jekyll\" class=\"heading-anchor\">Blog engine: Jekyll</a></h3><p>The website is generated through <a href=\"http://jekyllrb.com/\" rel=\"noopener\">Jekyll</a>. This is a very straightforward static site generator built with <a href=\"https://www.ruby-lang.org/\" rel=\"noopener\">Ruby</a> and has good support for blogging.</p><h4 id=\"templates-liquid\"><a href=\"#templates-liquid\" class=\"heading-anchor\">Templates: Liquid</a></h4><p>Don’t be fooled with the term ‘static site’. It certainly does not mean you have to write a full html page for each post. Jekyll uses the <a href=\"https://github.com/Shopify/liquid/wiki\" rel=\"noopener\">Liquid templating engine</a> and with it you can break down an html page in reusable components (for head, header, footer, content etc).</p><p>I used <a href=\"https://code.visualstudio.com/\" rel=\"noopener\">Visual Studio Code</a> to create the html components.</p><h4 id=\"content-markdown\"><a href=\"#content-markdown\" class=\"heading-anchor\">Content: Markdown</a></h4><p>Now comes the part which I’m most content with (pun intended). Jekyll supports <a href=\"http://daringfireball.net/projects/markdown/\" rel=\"noopener\">Markdown</a> as the format for the blog posts. I <strong>so</strong> like this format because of it’s ease of use and minimalism.</p><h4 id=\"responsive-design-and-style-skeleton\"><a href=\"#responsive-design-and-style-skeleton\" class=\"heading-anchor\">Responsive design &amp; style: Skeleton</a></h4><p>Since I didn’t choose one of the default Github Pages templates the blog looked very 90’s with only a plain Times New Roman font. That needed to change but I do like a very minimalistic style.</p><p>My front-end skills are quite limited so I looked for a very simple responsive boilerplate framework to work with. I decided to go for <a href=\"http://getskeleton.com/\" rel=\"noopener\">Skeleton</a>.</p><h2 id=\"i-blogging-again\"><a href=\"#i-blogging-again\" class=\"heading-anchor\">I ❤️ blogging again</a></h2><p>Now the blog looks good again and writing posts has become much simpler and more fun for me.</p><h3 id=\"my-blogging-workflow\"><a href=\"#my-blogging-workflow\" class=\"heading-anchor\">My blogging workflow</a></h3><ul class=\"list\"><li>I have a local (and up-to-date) repository of the remote <a href=\"https://github.com/marcduiker/marcduiker.github.io\" rel=\"noopener\"><code>marcduiker.github.io</code></a> repo.</li><li>I copy the default blog post Markdown file and give it a proper name according to the Jekyll naming convention.</li><li>I start editing the content of blog post file using <a href=\"http://markdownpad.com/\" rel=\"noopener\">MarkdownPad2</a>.</li><li>When I can’t finish the post in one go I save it in the <code>_drafts</code> folder.</li><li>When I finish the post I save it in the <code>_posts</code> folder. All markdown files in this folder will be publicly visible.</li><li>I commit &amp; push the post (and relevant assets) to the remote repository.</li></ul><p><strong>And that’s all there is.</strong></p><blockquote><p>Note: I only use MarkdownPad2 for editing and previewing the blog post content. Because I’m not running Jekyll on my local machine I can’t see a fully rendered page before actually pushing it online. Since it’s so quick to make a change and seeing the rendered result online I’m fine with this approach.</p></blockquote><h3 id=\"why-do-blogging-like-this\"><a href=\"#why-do-blogging-like-this\" class=\"heading-anchor\">Why do blogging like this</a></h3><p>The workflow and tooling involved feel very natural if you’re a developer. I also like the idea of having my content in version control.</p><p>So if you’re a developer who is either not blogging or want to switch to another blogging platform really have a look at <a href=\"https://pages.github.com/\" rel=\"noopener\">Github Pages</a>.</p><p>Expect some new Sitecore related posts from me soon! 😃</p>",
      "date_published": "2015-10-06T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/isolating-calls-to-sitecore-context-part-2/",
      "url": "https://marcduiker.dev/articles/isolating-calls-to-sitecore-context-part-2/",
      "title": "Isolating calls to Sitecore.Context for improved unit testability - Part II: ItemAdapter",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/11.norris_adapt-319w.webp 319w\" sizes=\"90vw\"><img src=\"/assets/images/11.norris_adapt-319w.webp\" width=\"319\" height=\"397\" alt=\"I don't adapt to my environment, my enviroment adapts to me.\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"recap-of-part-i\"><a href=\"#recap-of-part-i\" class=\"heading-anchor\">Recap of Part I</a></h2><p>This is part two of the “Isolating calls to Sitecore.Context…” series. If you haven’t read the <a href=\"/articles/isolating-calls-to-sitecore-context-part-1\">Part I</a> please do so to get the right context (pun intended).</p><p>In Part I <code>the GetItem()</code> method from <code>ItemProvider</code> returned an actual Sitecore Item. Because of the <code>IItemProvider</code> interface and Sitecore.FakeDb it is possible to return fake Sitecore items and no dependency to the Sitecore context is required in unit tests.</p><p>Although unit testing is now possible there are some (minor) downsides to them due to Sitecore.FakeDb:</p><ol class=\"list\"><li>Unit tests still require additional Sitecore assemblies and the Sitecore license file.</li><li>Unit tests look a bit cluttered due to setting up the fake Db and DbItem.</li><li>Unit tests are not very fast to execute.</li></ol><p>So lets look at another way of dealing with Sitecore items to get very lean unit tests.</p><h2 id=\"adapters\"><a href=\"#adapters\" class=\"heading-anchor\">Adapters</a></h2><p>I prefer to use abstractions of Sitecore objects because they make unit testing so much easier. The abstractions act as an adapter. It wraps the Sitecore object and exposes some frequently used properties and methods of that object. The adapter or wrapper pattern in combination with Sitecore is quite common and has been described earlier by several others (e.g. <a href=\"https://adeneys.wordpress.com/2012/04/13/mocking-sitecore/\" rel=\"noopener\">Alistair Deneys</a> and <a href=\"http://mhwelander.net/2014/04/30/unit-testing-sitecore-mvc/\" rel=\"noopener\">Martina Welander</a>).</p><p>So instead of working directly with a Sitecore <code>Item</code> we can work with an <code>IItemAdapter</code>interface which is implemented by the <code>ItemAdapter</code> type.</p><p><strong>IItemAdapter.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">System<span class=\"token punctuation\">.</span>Collections<span class=\"token punctuation\">.</span>Generic</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data<span class=\"token punctuation\">.</span>Items</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Adapters</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IItemAdapter</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> DisplayName <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n\n        <span class=\"token return-type class-name\">ID</span> Id <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n\n        <span class=\"token return-type class-name\">Item</span> InnerItem <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n\n        <span class=\"token return-type class-name\">ID</span> TemplateId <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n\n        <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">[</span><span class=\"token class-name\"><span class=\"token keyword\">string</span></span> fieldName<span class=\"token punctuation\">]</span> <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p><strong>ItemAdapter.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">System<span class=\"token punctuation\">.</span>Collections<span class=\"token punctuation\">.</span>Generic</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">System<span class=\"token punctuation\">.</span>Linq</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data<span class=\"token punctuation\">.</span>Items</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Diagnostics</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Adapters</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Adapters</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">ItemAdapter</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IItemAdapter</span></span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">public</span> <span class=\"token function\">ItemAdapter</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">Item</span> item<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            Assert<span class=\"token punctuation\">.</span><span class=\"token function\">ArgumentNotNull</span><span class=\"token punctuation\">(</span>item<span class=\"token punctuation\">,</span> <span class=\"token string\">\"item\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>InnerItem <span class=\"token operator\">=</span> item<span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> DisplayName\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">get</span> <span class=\"token punctuation\">{</span> <span class=\"token keyword\">return</span> InnerItem<span class=\"token punctuation\">.</span>DisplayName<span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">ID</span> Id\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">get</span> <span class=\"token punctuation\">{</span> <span class=\"token keyword\">return</span> InnerItem<span class=\"token punctuation\">.</span>ID<span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">Item</span> InnerItem\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">private</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">ID</span> TemplateId\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">get</span> <span class=\"token punctuation\">{</span> <span class=\"token keyword\">return</span> InnerItem<span class=\"token punctuation\">.</span>TemplateID<span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">[</span><span class=\"token class-name\"><span class=\"token keyword\">string</span></span> fieldName<span class=\"token punctuation\">]</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">get</span> <span class=\"token punctuation\">{</span> <span class=\"token keyword\">return</span> InnerItem<span class=\"token punctuation\">.</span>Fields<span class=\"token punctuation\">[</span>fieldName<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>Value<span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>Note that the original Sitecore <code>Item</code> is accessible through the <code>InnerItem</code> property.</p><p>Code that should be unit testable should rely only on the other properties the adapter exposes. Code that requires <code>Item</code> properties which are not exposed directly by the <code>ItemAdapter</code> (and don’t require unit testing) could use the <code>InnerItem</code> property.</p><p>Let’s have a look now at the new <code>IItemProvider</code> interface and <code>ItemProvider</code>implementation.</p><p><strong>IItemProvider.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data<span class=\"token punctuation\">.</span>Items</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Adapters</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Providers</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IItemProvider</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token return-type class-name\">Item</span> <span class=\"token function\">GetItem</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> itemId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n        <span class=\"token return-type class-name\">IItemAdapter</span> <span class=\"token function\">GetItemAdapter</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> itemId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p><strong>ItemProvider.ss</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data<span class=\"token punctuation\">.</span>Items</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Adapters</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Adapters</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Providers</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Providers</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">ItemProvider</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IItemProvider</span></span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">Item</span> <span class=\"token function\">GetItem</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> itemId<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">return</span> Sitecore<span class=\"token punctuation\">.</span>Context<span class=\"token punctuation\">.</span>Database<span class=\"token punctuation\">.</span><span class=\"token function\">GetItem</span><span class=\"token punctuation\">(</span>itemId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">IItemAdapter</span> <span class=\"token function\">GetItemAdapter</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> itemId<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> item <span class=\"token operator\">=</span> <span class=\"token function\">GetItem</span><span class=\"token punctuation\">(</span>itemId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">return</span> item <span class=\"token operator\">!=</span> <span class=\"token keyword\">null</span> <span class=\"token punctuation\">?</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">ItemAdapter</span><span class=\"token punctuation\">(</span>item<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">:</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>A new method is added called <code>GetItemAdapter()</code>. When in the web context the <code>ItemProvider</code> will call it’s own <code>GetItem()</code> method which will return an actual Sitecore <code>Item</code> and wrap it in an <code>ItemAdapter</code>. In a unit test context however <code>IItemProvider</code> will be mocked and the <code>GetItemAdapter()</code> method will be set-up to return a fake <code>ItemAdapter</code> (i.e. not based on a Sitecore <code>Item</code>).</p><p>Let’s recall the <code>AuthorProvider</code> example which was used in part I. Here’s the new <code>AuthorProvider</code> class where the <code>GetAuthorItem()</code> method now calls the <code>GetItemAdapter()</code> method of the <code>ItemProvider</code> and thus returning an <code>IItemAdapter</code>.</p><p><strong>AuthorProviderBasedOnItemAdapter.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Adapters</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Providers</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Models</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Providers</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">AuthorProviderBasedOnItemAdapter</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">private</span> <span class=\"token keyword\">readonly</span> <span class=\"token class-name\">IItemProvider</span> itemProvider<span class=\"token punctuation\">;</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token function\">AuthorProviderBasedOnItemAdapter</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IItemProvider</span> itemProvider<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>itemProvider <span class=\"token operator\">=</span> itemProvider<span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">Author</span> <span class=\"token function\">GetAuthor</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">string</span></span> authorId<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\">ID</span> parsedAuthorId<span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>ID<span class=\"token punctuation\">.</span><span class=\"token function\">TryParse</span><span class=\"token punctuation\">(</span>authorId<span class=\"token punctuation\">,</span> <span class=\"token keyword\">out</span> parsedAuthorId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n            <span class=\"token punctuation\">{</span>\n                <span class=\"token keyword\">return</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n\n            <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">GetAuthor</span><span class=\"token punctuation\">(</span>parsedAuthorId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">Author</span> <span class=\"token function\">GetAuthor</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> authorId<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> authorItem <span class=\"token operator\">=</span> <span class=\"token function\">GetAuthorItem</span><span class=\"token punctuation\">(</span>authorId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>authorItem <span class=\"token operator\">==</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span>\n            <span class=\"token punctuation\">{</span>\n                <span class=\"token keyword\">return</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n\n            <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Author</span>\n                       <span class=\"token punctuation\">{</span>\n                           Company <span class=\"token operator\">=</span> authorItem<span class=\"token punctuation\">[</span>Templates<span class=\"token punctuation\">.</span>AuthorTemplate<span class=\"token punctuation\">.</span>Fields<span class=\"token punctuation\">.</span>AuthorCompany<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n                           Name <span class=\"token operator\">=</span> authorItem<span class=\"token punctuation\">[</span>Templates<span class=\"token punctuation\">.</span>AuthorTemplate<span class=\"token punctuation\">.</span>Fields<span class=\"token punctuation\">.</span>AuthorName<span class=\"token punctuation\">]</span>\n                       <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">private</span> <span class=\"token return-type class-name\">IItemAdapter</span> <span class=\"token function\">GetAuthorItem</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> authorItemId<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">return</span> itemProvider<span class=\"token punctuation\">.</span><span class=\"token function\">GetItemAdapter</span><span class=\"token punctuation\">(</span>authorItemId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><h2 id=\"unit-tests-with-iitemadapter-and-moq\"><a href=\"#unit-tests-with-iitemadapter-and-moq\" class=\"heading-anchor\">Unit tests with IItemAdapter and Moq</a></h2><p>Here is the unit test for the <code>GetAuthor()</code>method when the <code>AuthorProvider</code> works with an <code>IItemAdapter</code>.</p><p><strong>AuthorProviderBasedOnItemAdapterTests.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">System</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Moq</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">NUnit<span class=\"token punctuation\">.</span>Framework</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Adapters</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Providers</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Models</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Providers</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Templates</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Test<span class=\"token punctuation\">.</span>Providers</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">/// &lt;summary&gt;</span>\n    <span class=\"token comment\">/// Unit tests for the AuthorProviderBasedOnItemAdapter.</span>\n    <span class=\"token comment\">/// &lt;/summary&gt;</span>\n    <span class=\"token punctuation\">[</span>TestFixture<span class=\"token punctuation\">]</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">AuthorProviderBasedOnItemAdapterTests</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">Test</span></span><span class=\"token punctuation\">]</span>\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">GetAuthor_WithValidAuthorBasedOnItemAdapter_ReturnsAuthorObject</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token comment\">// Arrange</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> authorItemId <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">ID</span><span class=\"token punctuation\">(</span>Guid<span class=\"token punctuation\">.</span><span class=\"token function\">NewGuid</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> authorItemMock <span class=\"token operator\">=</span> <span class=\"token function\">GetAuthorItemMock</span><span class=\"token punctuation\">(</span>authorItemId<span class=\"token punctuation\">,</span> <span class=\"token string\">\"John West\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Sitecore\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> itemProviderMock <span class=\"token operator\">=</span> <span class=\"token function\">GetItemProviderMock</span><span class=\"token punctuation\">(</span>authorItemMock<span class=\"token punctuation\">.</span>Object<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> authorProvider <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">AuthorProviderBasedOnItemAdapter</span><span class=\"token punctuation\">(</span>itemProviderMock<span class=\"token punctuation\">.</span>Object<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token comment\">// Act</span>\n            <span class=\"token class-name\">Author</span> result <span class=\"token operator\">=</span> authorProvider<span class=\"token punctuation\">.</span><span class=\"token function\">GetAuthor</span><span class=\"token punctuation\">(</span>authorItemId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token comment\">// Assert</span>\n            Assert<span class=\"token punctuation\">.</span><span class=\"token function\">AreEqual</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"John West\"</span><span class=\"token punctuation\">,</span> result<span class=\"token punctuation\">.</span>Name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">private</span> <span class=\"token return-type class-name\">Mock<span class=\"token punctuation\">&lt;</span>IItemProvider<span class=\"token punctuation\">&gt;</span></span> <span class=\"token function\">GetItemProviderMock</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IItemAdapter</span> authorItem<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> itemProviderMock <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Mock<span class=\"token punctuation\">&lt;</span>IItemProvider<span class=\"token punctuation\">&gt;</span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            itemProviderMock<span class=\"token punctuation\">.</span><span class=\"token function\">Setup</span><span class=\"token punctuation\">(</span>mock <span class=\"token operator\">=&gt;</span> mock<span class=\"token punctuation\">.</span><span class=\"token function\">GetItemAdapter</span><span class=\"token punctuation\">(</span>It<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">IsAny</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>ID<span class=\"token punctuation\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n                <span class=\"token punctuation\">.</span><span class=\"token function\">Returns</span><span class=\"token punctuation\">(</span>authorItem<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token keyword\">return</span> itemProviderMock<span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">Mock<span class=\"token punctuation\">&lt;</span>IItemAdapter<span class=\"token punctuation\">&gt;</span></span> <span class=\"token function\">GetAuthorItemMock</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> itemId<span class=\"token punctuation\">,</span> <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> authorName<span class=\"token punctuation\">,</span> <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> companyName<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> itemMock <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Mock<span class=\"token punctuation\">&lt;</span>IItemAdapter<span class=\"token punctuation\">&gt;</span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            itemMock<span class=\"token punctuation\">.</span><span class=\"token function\">SetupGet</span><span class=\"token punctuation\">(</span>mock <span class=\"token operator\">=&gt;</span> mock<span class=\"token punctuation\">.</span>TemplateId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Returns</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">ID</span><span class=\"token punctuation\">(</span>AuthorTemplate<span class=\"token punctuation\">.</span>TemplateId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            itemMock<span class=\"token punctuation\">.</span><span class=\"token function\">SetupGet</span><span class=\"token punctuation\">(</span>mock <span class=\"token operator\">=&gt;</span> mock<span class=\"token punctuation\">.</span>Id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Returns</span><span class=\"token punctuation\">(</span>itemId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            itemMock<span class=\"token punctuation\">.</span><span class=\"token function\">SetupGet</span><span class=\"token punctuation\">(</span>mock <span class=\"token operator\">=&gt;</span> mock<span class=\"token punctuation\">[</span>AuthorTemplate<span class=\"token punctuation\">.</span>Fields<span class=\"token punctuation\">.</span>AuthorName<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Returns</span><span class=\"token punctuation\">(</span>authorName<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            itemMock<span class=\"token punctuation\">.</span><span class=\"token function\">SetupGet</span><span class=\"token punctuation\">(</span>mock <span class=\"token operator\">=&gt;</span> mock<span class=\"token punctuation\">[</span>AuthorTemplate<span class=\"token punctuation\">.</span>Fields<span class=\"token punctuation\">.</span>AuthorCompany<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Returns</span><span class=\"token punctuation\">(</span>companyName<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token keyword\">return</span> itemMock<span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>When compared with the unit test in the first post (which used FakeDb) this unit test is slightly more compact and easier to understand. <strong>Don’t get me wrong, I really like Sitecore.FakeDb but use it only when you can’t use an adapter.</strong></p><p>Let’s look at the unit test in more detail.</p><h3 id=\"//-arrange\"><a href=\"#//-arrange\" class=\"heading-anchor\">// Arrange</a></h3><ul class=\"list\"><li>First a new <code>Id</code> is generated which will be used for the <code>IItemAdapter</code> mock.</li><li>The <code>GetAuthorItemMock()</code> method contructs a mock object (<code>authorItemMock</code>) based on <code>IItemAdapter</code> and requires parameters for the Id, author name and company name.</li><li>The <code>GetItemProviderMock()</code> method constructs a mock (<code>itemProviderMock</code>) based on <code>IItemProvider</code>. The <code>authorItemMock</code> is passed as a parameter since that will be the result of the <code>GetItemAdapter()</code> method of the mock.</li><li>An instance is created of the <code>AuthorProvider</code> and the <code>itemProviderMock</code> is passed in the constructor.</li></ul><h3 id=\"//-act\"><a href=\"#//-act\" class=\"heading-anchor\">// Act</a></h3><ul class=\"list\"><li>The <code>GetAuthor()</code> method on the <code>AuthorProvider</code> is called. Inside this method the <code>GetAuthorItem()</code> method is called which in turn executes the set-up <code>GetItemAdapter()</code> method of the mocked <code>IItemProvider</code>. A mocked <code>IItemAdapter</code> is returned and mapped to a new <code>Author</code> object.</li></ul><h3 id=\"//-assert\"><a href=\"#//-assert\" class=\"heading-anchor\">// Assert</a></h3><ul class=\"list\"><li>An assertion is done to check if the Name property of the <code>Author</code> object is equal to the author name field of the mocked <code>IItemAdapter</code>.</li></ul><h2 id=\"conclusion\"><a href=\"#conclusion\" class=\"heading-anchor\">Conclusion</a></h2><p>Creating adapters for Sitecore objects can be a relatively quick way to get unit testable code as long as dependency injection principles are used. You are in complete control of the adapter interface. You can start with a very lightweight interface and just expose a couple of properties you need for proper unit testing. Then you can gradually introduce additional properties to the interface as needed.</p><p>Next to the Sitecore <code>Item</code>, other frequently adapted Sitecore objects are <code>Database</code>, <code>Context</code> and <code>SiteContext</code>. More of that in a later post.</p><h2 id=\"source-code\"><a href=\"#source-code\" class=\"heading-anchor\">Source code</a></h2><p>The full source code that belongs to this post (and more) can be found on <a href=\"https://github.com/marcduiker/SitecorePlayground\" rel=\"noopener\">Github</a>.</p>",
      "date_published": "2014-11-22T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/isolating-calls-to-sitecore-context-part-1/",
      "url": "https://marcduiker.dev/articles/isolating-calls-to-sitecore-context-part-1/",
      "title": "Isolating calls to Sitecore.Context for improved unit testability - Part I: ItemProvider, Moq and FakeDb",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/10.vader_unit_tests-440w.webp 440w, /assets/images/10.vader_unit_tests-625w.webp 625w\" sizes=\"90vw\"><img src=\"/assets/images/10.vader_unit_tests-625w.webp\" width=\"625\" height=\"304\" alt=\"I find your lack of unit tests disturbing!\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"sitecore-projects-and-untestable-code\"><a href=\"#sitecore-projects-and-untestable-code\" class=\"heading-anchor\">Sitecore projects and (un)testable code</a></h2><p>Over the last years I’ve been involved with quite some Sitecore projects, some were true greenfield projects where a solution is created from scratch and some involved ‘only’ customizing components or extending the existing platform with new functionality. I enjoy both types of projects since they each have their challenges. I do want to share my concern from what I’ve seen in some of the latter solutions. Some things that all of these projects had in common were:</p><ol class=\"list\"><li>Little to no utilization of an ORM, such as <a href=\"https://marketplace.sitecore.net/Modules/Glass_Sitecore_Mapper.aspx?sc_lang=en\" rel=\"noopener\">Glass</a> <a href=\"https://github.com/kamsar/Synthesis\" rel=\"noopener\">Synthesis</a>, <a href=\"https://marketplace.sitecore.net/en/Modules/Compiled_Domain_Model.aspx\" rel=\"noopener\">CDM</a> (or a well defined self made solution).</li><li>Lack of proper testable code (no dependency injection).</li><li>Lack of unit tests</li></ol><p>Of course all these three points are related. If maintainability is important it is vital to any software project that code is written in such a way that it is unit testable. Although this post concerns isolating Sitecore, it could as well be about isolating calls to a custom database or to a logging component.</p><p>This post is intended as a practical guide for the ones involved with these ‘difficult’ projects and are strongly in favor of improving the code base in order to improve the testability and maintainability without spending many man months up front to make it happen.</p><h2 id=\"isolating-calls-to-the-sitecore-context\"><a href=\"#isolating-calls-to-the-sitecore-context\" class=\"heading-anchor\">Isolating calls to the Sitecore context</a></h2><p>The biggest problem I noticed with some Sitecore solutions is that calls to <code>Sitecore.Context.Database.GetItem()</code> are all over the place.</p><p>The first thing that can be done is to isolate these calls and put this in a custom <code>ItemProvider</code> class. (Note that Sitecore has its own <code>ItemProvider</code> class in the Sitecore.Kernel.dll but we’re not touching that one.)<br>So let’s start with the following very basic interface (<code>IItemProvider</code>) and implementation (<code>ItemProvider</code>). It will get more interesting later, I promise.</p><p><strong>IItemProvider.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data<span class=\"token punctuation\">.</span>Items</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Providers</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IItemProvider</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token return-type class-name\">Item</span> <span class=\"token function\">GetItem</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> itemId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p><strong>ItemProvider.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data<span class=\"token punctuation\">.</span>Items</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Providers</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Providers</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">ItemProvider</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IItemProvider</span></span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">Item</span> <span class=\"token function\">GetItem</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> itemId<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">return</span> Sitecore<span class=\"token punctuation\">.</span>Context<span class=\"token punctuation\">.</span>Database<span class=\"token punctuation\">.</span><span class=\"token function\">GetItem</span><span class=\"token punctuation\">(</span>itemId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>In every Sitecore solution C# models are used which are based on Sitecore templates. Let’s assume we are dealing with the following <code>Author</code> object in C#.</p><p><strong>Author.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Models</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Author</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> Name <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> Company <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p><code>Author</code> instances are usually retrieved via a specific provider such as the <code>AuthorProvider</code> below. (The class name in the gist below is a bit longer because I’ll show another flavor of this provider in a next post).</p><p><strong>AuthorProviderBasedOnRegularItem.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data<span class=\"token punctuation\">.</span>Items</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Providers</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Models</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Providers</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">AuthorProviderBasedOnRegularItem</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">private</span> <span class=\"token keyword\">readonly</span> <span class=\"token class-name\">IItemProvider</span> itemProvider<span class=\"token punctuation\">;</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token function\">AuthorProviderBasedOnRegularItem</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IItemProvider</span> itemProvider<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>itemProvider <span class=\"token operator\">=</span> itemProvider<span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">Author</span> <span class=\"token function\">GetAuthor</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">string</span></span> authorId<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\">ID</span> parsedAuthorId<span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>ID<span class=\"token punctuation\">.</span><span class=\"token function\">TryParse</span><span class=\"token punctuation\">(</span>authorId<span class=\"token punctuation\">,</span> <span class=\"token keyword\">out</span> parsedAuthorId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n            <span class=\"token punctuation\">{</span>\n                <span class=\"token keyword\">return</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n\n            <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">GetAuthor</span><span class=\"token punctuation\">(</span>parsedAuthorId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">Author</span> <span class=\"token function\">GetAuthor</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> authorId<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> authorItem <span class=\"token operator\">=</span> <span class=\"token function\">GetAuthorItem</span><span class=\"token punctuation\">(</span>authorId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>authorItem <span class=\"token operator\">==</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span>\n            <span class=\"token punctuation\">{</span>\n                <span class=\"token keyword\">return</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n\n            <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Author</span>\n            <span class=\"token punctuation\">{</span>\n                Company <span class=\"token operator\">=</span> authorItem<span class=\"token punctuation\">[</span>Templates<span class=\"token punctuation\">.</span>AuthorTemplate<span class=\"token punctuation\">.</span>Fields<span class=\"token punctuation\">.</span>AuthorCompany<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n                Name <span class=\"token operator\">=</span> authorItem<span class=\"token punctuation\">[</span>Templates<span class=\"token punctuation\">.</span>AuthorTemplate<span class=\"token punctuation\">.</span>Fields<span class=\"token punctuation\">.</span>AuthorName<span class=\"token punctuation\">]</span>\n            <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">private</span> <span class=\"token return-type class-name\">Item</span> <span class=\"token function\">GetAuthorItem</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ID</span> authorItemId<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">return</span> itemProvider<span class=\"token punctuation\">.</span><span class=\"token function\">GetItem</span><span class=\"token punctuation\">(</span>authorItemId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>Notice that the constructor of <code>AuthorProvider</code> requires an instance of a type that implements <code>IItemProvider</code> (this is an example of constructor injection). The <code>GetAuthorItem()</code> method calls the <code>GetItem()</code> method on the <code>IItemProvider</code> and this construction enables us to unit test the <code>AuthorProvider</code> using <a href=\"https://github.com/sergeyshushlyapin/Sitecore.FakeDb\" rel=\"noopener\">Sitecore.FakeDb</a> and <a href=\"https://github.com/Moq/moq4\" rel=\"noopener\">Moq</a> (or any other mocking framework you prefer).</p><h2 id=\"unit-tests-with-sitecorefakedb-and-moq\"><a href=\"#unit-tests-with-sitecorefakedb-and-moq\" class=\"heading-anchor\">Unit tests with Sitecore.FakeDb and Moq</a></h2><p>Sitecore.FakeDb is a very nice unit testing framework which allows creation and manipulation of Sitecore items in <a href=\"http://memory.It\" rel=\"noopener\">memory.It</a>’s quite easy to get started with. Just install the <a href=\"https://www.nuget.org/packages/Sitecore.FakeDb/\" rel=\"noopener\">NuGet package</a> and follow the <a href=\"https://github.com/sergeyshushlyapin/Sitecore.FakeDb/wiki/Installation\" rel=\"noopener\">instructions</a> carefully because some Sitecore &amp; Lucene assemblies and a valid Sitecore license are required. In the example below I only use FakeDb as a source for getting Sitecore items.<br><a href=\"http://www.nuget.org/packages/moq\" rel=\"noopener\">Moq</a> is a very popular mocking framework. If you don’t know it make sure you read at least the <a href=\"https://github.com/Moq/moq4/wiki/Quickstart\" rel=\"noopener\">quickstart</a>.</p><p>Here is the unit test for the <code>AuthorProvider.GetAuthor()</code> method.</p><p><strong>AuthorProviderBasedOnRegularItem.cs</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">System</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Moq</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">NUnit<span class=\"token punctuation\">.</span>Framework</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>Data<span class=\"token punctuation\">.</span>Items</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">Sitecore<span class=\"token punctuation\">.</span>FakeDb</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>Common<span class=\"token punctuation\">.</span>Interfaces<span class=\"token punctuation\">.</span>Providers</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Models</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Providers</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Templates</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token namespace\">SitecorePlayground<span class=\"token punctuation\">.</span>News<span class=\"token punctuation\">.</span>Test<span class=\"token punctuation\">.</span>Providers</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">/// &lt;summary&gt;</span>\n    <span class=\"token comment\">/// Unit tests for the AuthorProviderBasedOnRegularItem class.</span>\n    <span class=\"token comment\">/// &lt;/summary&gt;</span>\n    <span class=\"token punctuation\">[</span>TestFixture<span class=\"token punctuation\">]</span>\n    <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">Category</span><span class=\"token attribute-arguments\"><span class=\"token punctuation\">(</span><span class=\"token string\">\"Requires Sitecore.FakeDb and Sitecore license\"</span><span class=\"token punctuation\">)</span></span></span><span class=\"token punctuation\">]</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">AuthorProviderBasedOnRegularItemTests</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">Test</span></span><span class=\"token punctuation\">]</span>\n        <span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">GetAuthor_WithValidAuthorBasedOnRegularItem_ReturnsAuthorObject</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">using</span> <span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> fakeDb <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Db</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n            <span class=\"token punctuation\">{</span>\n                <span class=\"token comment\">// Arrange</span>\n                <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> authorId <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">ID</span><span class=\"token punctuation\">(</span>Guid<span class=\"token punctuation\">.</span><span class=\"token function\">NewGuid</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> templateId <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">ID</span><span class=\"token punctuation\">(</span>AuthorTemplate<span class=\"token punctuation\">.</span>TemplateId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                <span class=\"token class-name\">DbItem</span> fakeDbItem <span class=\"token operator\">=</span> <span class=\"token function\">GetFakeAuthorDbItem</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"John West\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Sitecore\"</span><span class=\"token punctuation\">,</span> authorId<span class=\"token punctuation\">,</span> templateId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                fakeDb<span class=\"token punctuation\">.</span><span class=\"token function\">Add</span><span class=\"token punctuation\">(</span>fakeDbItem<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> fakeAuthorItem <span class=\"token operator\">=</span> fakeDb<span class=\"token punctuation\">.</span><span class=\"token function\">GetItem</span><span class=\"token punctuation\">(</span>authorId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> itemProviderMock <span class=\"token operator\">=</span> <span class=\"token function\">GetItemProviderMock</span><span class=\"token punctuation\">(</span>fakeAuthorItem<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> authorProvider <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">AuthorProviderBasedOnRegularItem</span><span class=\"token punctuation\">(</span>itemProviderMock<span class=\"token punctuation\">.</span>Object<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n                <span class=\"token comment\">// Act</span>\n                <span class=\"token class-name\">Author</span> result <span class=\"token operator\">=</span> authorProvider<span class=\"token punctuation\">.</span><span class=\"token function\">GetAuthor</span><span class=\"token punctuation\">(</span>authorId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n                <span class=\"token comment\">// Assert</span>\n                Assert<span class=\"token punctuation\">.</span><span class=\"token function\">AreEqual</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"John West\"</span><span class=\"token punctuation\">,</span> result<span class=\"token punctuation\">.</span>Name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">private</span> <span class=\"token return-type class-name\">DbItem</span> <span class=\"token function\">GetFakeAuthorDbItem</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">string</span></span> authorName<span class=\"token punctuation\">,</span> <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> authorCompany<span class=\"token punctuation\">,</span> <span class=\"token class-name\">ID</span> itemId<span class=\"token punctuation\">,</span> <span class=\"token class-name\">ID</span> templateId<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">DbItem</span><span class=\"token punctuation\">(</span>authorName<span class=\"token punctuation\">,</span> itemId<span class=\"token punctuation\">,</span> templateId<span class=\"token punctuation\">)</span>\n                       <span class=\"token punctuation\">{</span>\n                           <span class=\"token punctuation\">{</span> AuthorTemplate<span class=\"token punctuation\">.</span>Fields<span class=\"token punctuation\">.</span>AuthorName<span class=\"token punctuation\">,</span> authorName <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> \n                           <span class=\"token punctuation\">{</span> AuthorTemplate<span class=\"token punctuation\">.</span>Fields<span class=\"token punctuation\">.</span>AuthorCompany<span class=\"token punctuation\">,</span> authorCompany <span class=\"token punctuation\">}</span>\n                       <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">private</span> <span class=\"token return-type class-name\">Mock<span class=\"token punctuation\">&lt;</span>IItemProvider<span class=\"token punctuation\">&gt;</span></span> <span class=\"token function\">GetItemProviderMock</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">Item</span> authorItem<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> itemProviderMock <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Mock<span class=\"token punctuation\">&lt;</span>IItemProvider<span class=\"token punctuation\">&gt;</span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            itemProviderMock<span class=\"token punctuation\">.</span><span class=\"token function\">Setup</span><span class=\"token punctuation\">(</span>mock <span class=\"token operator\">=&gt;</span> mock<span class=\"token punctuation\">.</span><span class=\"token function\">GetItem</span><span class=\"token punctuation\">(</span>It<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">IsAny</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>ID<span class=\"token punctuation\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Returns</span><span class=\"token punctuation\">(</span>authorItem<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token keyword\">return</span> itemProviderMock<span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>The goal of the unit test is to verify if the <code>GetAuthor()</code> method of the <code>AuthorProvider</code> returns an Author object when a Sitecore item Id is passed in as a parameter. When the <code>AuthorProvider</code> is used in a website context an <code>ItemProvider</code> is passed into the constructor of the <code>AuthorProvider</code> and the item is retrieved from the Sitecore context. In the unit test however we don’t want any dependency on the Sitecore context. Therefore an mock is created based on the <code>IItemProvider</code>interface. We can set-up the <code>GetItem()</code> method on the mock to return a fake Sitecore item which we will get from Sitecore.FakeDb.</p><p>Let’s look at the the unit test in more detail.</p><h3 id=\"//-arrange\"><a href=\"#//-arrange\" class=\"heading-anchor\">// Arrange</a></h3><ul class=\"list\"><li>Since FakeDb is an in memory database an instance is created inside a using statement. This ensures that the in memory database is disposed properly after running the unit test.</li><li>In order to create a new <code>DbItem</code> (from Sitecore.FakeDb) an item Name, Id and Template Id are required. The <code>GetFakeAuthorDbItem</code> method constructs the <code>DbItem</code> with fields for the author name and the company.</li><li>Once the <code>DbItem</code> is created and added to the FakeDb instance we retrieve the Sitecore item (<code>fakeAuthorItem</code>) from FakeDb.</li><li>Next an <code>itemProviderMock</code> object is created based on the <code>IItemProvider</code> and the <code>fakeAuthorItem</code> is passed since that is used as the returning item for the mocked <code>GetItem()</code>method (see the <code>GetItemProviderMock</code> method how that is set-up).</li><li>In the final line of the Arrange section an instance of the <code>AuthorProvider</code> is created and the <code>itemProviderMock</code> is passed in the constructor.</li></ul><h3 id=\"//-act\"><a href=\"#//-act\" class=\"heading-anchor\">// Act</a></h3><ul class=\"list\"><li>The <code>GetAuthor()</code> method on the <code>AuthorProvider</code> is called. Inside this method the <code>GetAuthorItem()</code> method is called which in turn executes the set-up <code>GetItem()</code> method of the mocked <code>IItemProvider</code>. A Sitecore item (from FakeDb) is returned and mapped to a new <code>Author</code> object.</li></ul><h3 id=\"//-assert\"><a href=\"#//-assert\" class=\"heading-anchor\">// Assert</a></h3><ul class=\"list\"><li>An assertion is done to check if the Name property of the <code>Author</code> object is equal to the author name field of the Sitecore item.</li></ul><h2 id=\"conclusion\"><a href=\"#conclusion\" class=\"heading-anchor\">Conclusion</a></h2><p>Though this example is fairly straightforward it demonstrates how to write testable code when you’re dealing with Sitecore projects. Writing testable code and using a mocking framework in combination with Sitecore.FakeDb in unit tests can be a bit of a learning curve but I consider these as must have skills for any Sitecore developer these days.</p><p>In the <a href=\"/articles/isolating-calls-to-sitecore-context-part-2\">next post</a> I’ll show a similar approach with an <code>ItemProvider</code> that uses an <code>ItemAdapter</code> instead of a regular Sitecore item.</p><h2 id=\"source-code\"><a href=\"#source-code\" class=\"heading-anchor\">Source code</a></h2><p>The full source code that is used in this post (and lots more) is on <a href=\"https://github.com/marcduiker/SitecorePlayground\" rel=\"noopener\">GitHub</a>. Feel free to poke at it and suggest improvements.</p>",
      "date_published": "2014-11-18T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/sitecore-mvp-summit-cocktail-workshop/",
      "url": "https://marcduiker.dev/articles/sitecore-mvp-summit-cocktail-workshop/",
      "title": "Sitecore MVP Summit Cocktail Workshop (photo impression)",
      "content_html": "<h2 id=\"mvp-summit-barcelona\"><a href=\"#mvp-summit-barcelona\" class=\"heading-anchor\">MVP Summit Barcelona</a></h2><p>Directly after Sitecore Symposium Barcelona had finished the Sitecore MVP Summit started. And it started in style. We were taken to <a href=\"http://www.javierdelasmuelas.com/eng/dry/barcelona/the-academy-v2\" rel=\"noopener\">Dry Martini The Academy</a> where a cocktail making workshop was organised. The evening was completed with a excellent dinner at <a href=\"http://www.speakeasy-bcn.com/en/\" rel=\"noopener\">Speakeasy</a>.</p><p>Here’s a small impression of the evening. If you want to see all the pictures (and you should 😃 check the link at the bottom of this post.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.1.sitecoremvpcocktailparty-1-440w.webp 440w, /assets/images/9.1.sitecoremvpcocktailparty-1-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.1.sitecoremvpcocktailparty-1-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.2.sitecoremvpcocktailparty-2-440w.webp 440w, /assets/images/9.2.sitecoremvpcocktailparty-2-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.2.sitecoremvpcocktailparty-2-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.3.sitecoremvpcocktailparty-3-440w.webp 440w, /assets/images/9.3.sitecoremvpcocktailparty-3-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.3.sitecoremvpcocktailparty-3-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.4.sitecoremvpcocktailparty-4-440w.webp 440w, /assets/images/9.4.sitecoremvpcocktailparty-4-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.4.sitecoremvpcocktailparty-4-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.5.sitecoremvpcocktailparty-5-440w.webp 440w, /assets/images/9.5.sitecoremvpcocktailparty-5-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.5.sitecoremvpcocktailparty-5-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.6.sitecoremvpcocktailparty-6-440w.webp 440w, /assets/images/9.6.sitecoremvpcocktailparty-6-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.6.sitecoremvpcocktailparty-6-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.7.sitecoremvpcocktailparty-7-440w.webp 440w, /assets/images/9.7.sitecoremvpcocktailparty-7-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.7.sitecoremvpcocktailparty-7-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.8.sitecoremvpcocktailparty-8-440w.webp 440w, /assets/images/9.8.sitecoremvpcocktailparty-8-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.8.sitecoremvpcocktailparty-8-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.9.sitecoremvpcocktailparty-9-440w.webp 440w, /assets/images/9.9.sitecoremvpcocktailparty-9-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.9.sitecoremvpcocktailparty-9-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.10.sitecoremvpcocktailparty-10-440w.webp 440w, /assets/images/9.10.sitecoremvpcocktailparty-10-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.10.sitecoremvpcocktailparty-10-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.11.sitecoremvpcocktailparty-11-440w.webp 440w, /assets/images/9.11.sitecoremvpcocktailparty-11-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.11.sitecoremvpcocktailparty-11-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/9.12.sitecoremvpcocktailparty-12-440w.webp 440w, /assets/images/9.12.sitecoremvpcocktailparty-12-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/9.12.sitecoremvpcocktailparty-12-650w.webp\" width=\"650\" height=\"365\" alt=\"Sitecore MVP cocktail workshop\" loading=\"lazy\" decoding=\"async\"></picture></p><p>See the full set of pictures (43) on <a href=\"https://onedrive.live.com/redir?resid=89069150F6445DF7!2215&amp;authkey=!ANDiMLnh4xaEhC8&amp;ithint=folder%2cjpg\" rel=\"noopener\">OneDrive</a>.</p>",
      "date_published": "2014-10-01T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/sitecore-symposium-2014-keynote-session/",
      "url": "https://marcduiker.dev/articles/sitecore-symposium-2014-keynote-session/",
      "title": "Sitecore Symposium 2014 - Keynote Session (photo impression)",
      "content_html": "<h2 id=\"barcelona-keynote-session\"><a href=\"#barcelona-keynote-session\" class=\"heading-anchor\">Barcelona Keynote Session</a></h2><p>Michael Seifert, the CEO of Sitecore, opened the Symposium with an excellent keynote about experiences and its role in the current (and future) digital landscape. Experiences should be collected and can actually be shaped for marketing purposes. Michael showcased how Sitecore 8 can help organisations in shaping experiences for their audience.</p><p>Here’s an impression of the keynote session.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/8.1.sitecoresymposium-01-440w.webp 440w, /assets/images/8.1.sitecoresymposium-01-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/8.1.sitecoresymposium-01-650w.webp\" width=\"650\" height=\"487\" alt=\"Sitecore Symposium 1\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/8.2.sitecoresymposium-02-440w.webp 440w, /assets/images/8.2.sitecoresymposium-02-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/8.2.sitecoresymposium-02-650w.webp\" width=\"650\" height=\"487\" alt=\"Sitecore Symposium 2\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/8.3.sitecoresymposium-03-440w.webp 440w, /assets/images/8.3.sitecoresymposium-03-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/8.3.sitecoresymposium-03-650w.webp\" width=\"650\" height=\"487\" alt=\"Sitecore Symposium 3\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/8.4.sitecoresymposium-04-440w.webp 440w, /assets/images/8.4.sitecoresymposium-04-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/8.4.sitecoresymposium-04-650w.webp\" width=\"650\" height=\"487\" alt=\"Sitecore Symposium 4\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/8.5.sitecoresymposium-05-440w.webp 440w, /assets/images/8.5.sitecoresymposium-05-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/8.5.sitecoresymposium-05-650w.webp\" width=\"650\" height=\"487\" alt=\"Sitecore Symposium 5\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/8.6.sitecoresymposium-06-440w.webp 440w, /assets/images/8.6.sitecoresymposium-06-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/8.6.sitecoresymposium-06-650w.webp\" width=\"650\" height=\"487\" alt=\"Sitecore Symposium 6\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/8.7.sitecoresymposium-07-440w.webp 440w, /assets/images/8.7.sitecoresymposium-07-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/8.7.sitecoresymposium-07-650w.webp\" width=\"650\" height=\"487\" alt=\"Sitecore Symposium 7\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/8.8.sitecoresymposium-08-440w.webp 440w, /assets/images/8.8.sitecoresymposium-08-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/8.8.sitecoresymposium-08-650w.webp\" width=\"650\" height=\"487\" alt=\"Sitecore Symposium 8\" loading=\"lazy\" decoding=\"async\"></picture><br><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/8.9.sitecoresymposium-09-440w.webp 440w, /assets/images/8.9.sitecoresymposium-09-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/8.9.sitecoresymposium-09-650w.webp\" width=\"650\" height=\"487\" alt=\"Sitecore Symposium 9\" loading=\"lazy\" decoding=\"async\"></picture></p>",
      "date_published": "2014-09-23T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/sitecore-mvp-summit-dev-podcasts-and-newsletter/",
      "url": "https://marcduiker.dev/articles/sitecore-mvp-summit-dev-podcasts-and-newsletter/",
      "title": "Sitecore MVP Summit, Dev Podcasts and Newsletter",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/7.sitecore_mvp_summit_2013-440w.webp 440w, /assets/images/7.sitecore_mvp_summit_2013-640w.webp 640w\" sizes=\"90vw\"><img src=\"/assets/images/7.sitecore_mvp_summit_2013-640w.webp\" width=\"640\" height=\"386\" alt=\"Sitecore MVP Summit 2013\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"sitecore-mvp-summit-miami\"><a href=\"#sitecore-mvp-summit-miami\" class=\"heading-anchor\">Sitecore MVP Summit Miami</a></h2><p>This week more than 60 Sitecore MVPs from 10 different countries will attend the first global <a href=\"http://sitecoremvpsummit.net/\" rel=\"noopener\">Sitecore MVP</a> summit. I consider myself very fortunate to be one of them 😃.</p><p>There are quite a few Dutch MVPs and many of them are able to join the summit in Miami. For the non-Dutch MVPs who are attending, I’m pretty sure you’ll be able to recognize us 😉. (Make sure to check <a href=\"https://twitter.com/search?q=%23sitecoremvpsummit\" rel=\"noopener\">#sitecoremvpsummit</a> on Twitter the next few days!).</p><h2 id=\"travel-and-learn\"><a href=\"#travel-and-learn\" class=\"heading-anchor\">Travel &amp; learn</a></h2><p>Since I’ll be spending quite some time travelling from The Netherlands to the US I thought I can use this time efficiently and catch up with some of the latest software engineering &amp; .Net development news by listening to podcasts. So I installed <a href=\"https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict\" rel=\"noopener\">Podcast Addict</a> (for Android) on my phone and added the following feeds:</p><ul class=\"list\"><li><a href=\"http://herdingcode.com/\" rel=\"noopener\">Herding Code</a></li><li><a href=\"http://www.hanselminutes.com/\" rel=\"noopener\">HanselMinutes</a></li><li><a href=\"http://www.se-radio.net/\" rel=\"noopener\">Software Engineering Radio</a></li><li><a href=\"http://integrumtech.com/category/agile-weekly-podcast/\" rel=\"noopener\">Agile Weekly</a></li><li><a href=\"http://channel9.msdn.com/Feeds/RSS\" rel=\"noopener\">Channel 9</a></li></ul><p>Unfortunately I didn’t find any active Sitecore related feeds (there used to be one on <a href=\"http://learnsitecore.cmsuniverse.net/\" rel=\"noopener\">http://learnsitecore.cmsuniverse.net</a>). What I did find was a post describing <a href=\"http://ihuangsitecore.blogspot.nl/2013/06/sitecore-podcast-feed.html\" rel=\"noopener\">how to build a podcast feed in Sitecore</a>.</p><h1 id=\"sitecore-dev-news\"><a href=\"#sitecore-dev-news\" class=\"heading-anchor\">Sitecore dev news</a></h1><p>If you want to stay up to date on the latest Sitecore development topics you really need to subscribe to the <a href=\"http://tinyletter.com/sitecorebasics\" rel=\"noopener\">Sitecore Basics Weekly Newsletter</a> (an excellent initiative by <a href=\"https://twitter.com/kiranpatils\" rel=\"noopener\">Kiran Patil</a>). In addition it is very useful (and great fun) to watch the <a href=\"http://www.youtube.com/user/SitecorePM\" rel=\"noopener\">Sitecore 7 videos on Youtube</a> or watch them live on <a href=\"https://plus.google.com/+sitecore/posts\" rel=\"noopener\">Google+</a>.</p>",
      "date_published": "2013-11-11T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/lazy-programmers-use-code-snippets/",
      "url": "https://marcduiker.dev/articles/lazy-programmers-use-code-snippets/",
      "title": "Lazy Programmers User Code Snippets",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/6.propfull_snippet-362w.webp 362w\" sizes=\"90vw\"><img src=\"/assets/images/6.propfull_snippet-362w.webp\" width=\"362\" height=\"130\" alt=\"Full property snippet\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"the-best-programmers-are-lazy\"><a href=\"#the-best-programmers-are-lazy\" class=\"heading-anchor\">The best programmers are lazy</a></h2><p>The great <a href=\"http://en.wikipedia.org/wiki/Larry_Wall\" rel=\"noopener\">Larry Wall</a> (author of Perl) claimed that laziness is one of the greatest virtues a programmer could develop. The best programmers are lazy in the sense that they do not write duplicate code or take pleasure in writing boilerplate code. They are efficient in writing code that follows the <a href=\"http://en.wikipedia.org/wiki/Don't_repeat_yourself\" rel=\"noopener\">DRY</a>, <a href=\"http://en.wikipedia.org/wiki/KISS_principle\" rel=\"noopener\">KISS</a> and <a href=\"http://en.wikipedia.org/wiki/You_aren't_gonna_need_it\" rel=\"noopener\">YAGNI</a> principles.</p><h3 id=\"efficient-ide-usage\"><a href=\"#efficient-ide-usage\" class=\"heading-anchor\">Efficient IDE usage</a></h3><p>Next to writing as few lines of code as possible (without compromising on readability ofcourse), being a lazy programmer also means using the IDE efficiently.</p><p>This means:</p><ol class=\"list\"><li>Knowing when and how to use the various project and project item templates.</li><li>Using keyboard shortcuts instead of navigating through long menus.</li><li>Using code snippets instead of writing boilerplate code.</li></ol><h3 id=\"code-snippets\"><a href=\"#code-snippets\" class=\"heading-anchor\">Code Snippets</a></h3><p>Ever since the 2005 edition, Visual Studio allows inserting blocks of reusable code, called code snippets, through shortcut keys (or the context menu, but truly lazy programmers don’t take away their hands off the keyboard).</p><p>I’m still amazed by the fact that so many .Net developers are still not aware of these code snippets. Using these can really speed up writing commonly used bits of code like properties with backing fields, try/catch blocks, foreach loops etc.</p><p><strong>So if you want to be a lazy programmer learn these snippet shortcuts by heart!</strong></p><ol class=\"list\"><li><code>ctor</code> - constructor</li><li><code>prop</code> - auto implemented property</li><li><code>propg</code> - auto implemented property with private setter</li><li><code>propfull</code> - property with backing field</li><li><code>if</code> - if block</li><li><code>else</code> - else block</li><li><code>try</code> - try/catch block</li><li><code>tryf</code> - try/finally block (oddly enough there is no trycf!)</li><li><code>for</code> - for loop</li><li><code>forr</code> - reverse for loop</li><li><code>foreach</code> - foreach loop</li><li><code>switch</code> - switch block</li></ol><p>Simply invoke the snippet by typing the shortcut followed by <code>[TAB]</code>. <a href=\"http://msdn.microsoft.com/library/z41h7fat.aspx\" rel=\"noopener\">Here’s a list</a> with more built-in shortcuts if you’re eager for more.</p><h3 id=\"get-lazier-write-your-own-snippets\"><a href=\"#get-lazier-write-your-own-snippets\" class=\"heading-anchor\">Get lazier: write your own snippets!</a></h3><p>If you want to be as lazy as me you can easily <a href=\"http://msdn.microsoft.com/library/ms165394.aspx\" rel=\"noopener\">write your own snippets</a> as I’ve done for <a href=\"/articles/visual-studio-snippet-for-extension-methods\">extension methods</a>, <a href=\"/articles/visual-studio-snippets-for-unit-test-methods\">unit tests</a> and more. The <a href=\"http://visualstudiogallery.msdn.microsoft.com/B08B0375-139E-41D7-AF9B-FAEE50F68392\" rel=\"noopener\">Snippet Designer plugin</a> can be useful if you don’t like editing the raw XML.</p><p>Enjoy lazy coding!</p>",
      "date_published": "2013-07-07T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/visual-studio-snippet-for-extension-methods/",
      "url": "https://marcduiker.dev/articles/visual-studio-snippet-for-extension-methods/",
      "title": "Visual Studio Snippet for Extension Methods",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/5.extension_method_snippet-406w.webp 406w\" sizes=\"90vw\"><img src=\"/assets/images/5.extension_method_snippet-406w.webp\" width=\"406\" height=\"81\" alt=\"Extension method snippet\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"extension-methods\"><a href=\"#extension-methods\" class=\"heading-anchor\">Extension methods</a></h2><p>Using extension methods in Visual Studio can really help to reduce writing boilerplate code. Here’s a simple snippet which can be used to create extension methods.</p><pre class=\"language-xml\"><code class=\"language-xml\"><span class=\"token prolog\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;</span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>CodeSnippets</span> <span class=\"token attr-name\">xmlns</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">&gt;</span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>CodeSnippet</span> <span class=\"token attr-name\">Format</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>1.0.0<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">&gt;</span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Header</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>SnippetTypes</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>SnippetType</span><span class=\"token punctuation\">&gt;</span></span>Expansion<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>SnippetType</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>SnippetTypes</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Title</span><span class=\"token punctuation\">&gt;</span></span>Extension Method<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Title</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Author</span><span class=\"token punctuation\">&gt;</span></span>Marc Duiker<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Author</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Description</span><span class=\"token punctuation\">&gt;</span></span>Snippet to create an extension method. \n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Description</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>HelpUrl</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>HelpUrl</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Shortcut</span><span class=\"token punctuation\">&gt;</span></span>exm<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Shortcut</span><span class=\"token punctuation\">&gt;</span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Header</span><span class=\"token punctuation\">&gt;</span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Snippet</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Declarations</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Literal</span> <span class=\"token attr-name\">Editable</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>true<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ID</span><span class=\"token punctuation\">&gt;</span></span>TypeOut<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ID</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ToolTip</span><span class=\"token punctuation\">&gt;</span></span>TypeOut<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ToolTip</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Default</span><span class=\"token punctuation\">&gt;</span></span>TypeOut<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Default</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Function</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Function</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Literal</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Literal</span> <span class=\"token attr-name\">Editable</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>true<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ID</span><span class=\"token punctuation\">&gt;</span></span>MethodName<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ID</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ToolTip</span><span class=\"token punctuation\">&gt;</span></span>MethodName<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ToolTip</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Default</span><span class=\"token punctuation\">&gt;</span></span>MethodName<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Default</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Function</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Function</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Literal</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Literal</span> <span class=\"token attr-name\">Editable</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>true<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ID</span><span class=\"token punctuation\">&gt;</span></span>TypeIn<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ID</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ToolTip</span><span class=\"token punctuation\">&gt;</span></span>TypeIn<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ToolTip</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Default</span><span class=\"token punctuation\">&gt;</span></span>TypeIn<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Default</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Function</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Function</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Literal</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Literal</span> <span class=\"token attr-name\">Editable</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>true<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ID</span><span class=\"token punctuation\">&gt;</span></span>parameter<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ID</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ToolTip</span><span class=\"token punctuation\">&gt;</span></span>parameter<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ToolTip</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Default</span><span class=\"token punctuation\">&gt;</span></span>parameter<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Default</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Function</span><span class=\"token punctuation\">&gt;</span></span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Function</span><span class=\"token punctuation\">&gt;</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Literal</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Declarations</span><span class=\"token punctuation\">&gt;</span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>Code</span> <span class=\"token attr-name\">Language</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>csharp<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">&gt;</span></span><span class=\"token cdata\">&lt;![CDATA[public static $TypeOut$ $MethodName$(this $TypeIn$ $parameter$)\n{\n    $end$\n}]]&gt;</span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Code</span><span class=\"token punctuation\">&gt;</span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>Snippet</span><span class=\"token punctuation\">&gt;</span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>CodeSnippet</span><span class=\"token punctuation\">&gt;</span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>CodeSnippets</span><span class=\"token punctuation\">&gt;</span></span></code></pre><p><a href=\"https://gist.github.com/marcduiker/64a512a057644533eefc.js\" rel=\"noopener\">Source</a></p><p>After importing the snippet via <em>Tools &gt; Code Snippets Manager</em> you can use it by typing <code>exm[TAB]</code>.</p>",
      "date_published": "2013-07-04T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/sitecore-introduction-for-developers/",
      "url": "https://marcduiker.dev/articles/sitecore-introduction-for-developers/",
      "title": "Sitecore Introduction for Developers",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/4.1.sitecore_intro-400w.webp 400w\" sizes=\"90vw\"><img src=\"/assets/images/4.1.sitecore_intro-400w.webp\" width=\"400\" height=\"286\" alt loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"techtalk\"><a href=\"#techtalk\" class=\"heading-anchor\">TechTalk</a></h2><p>At a recent TechTalk at my employer I gave a presentation about <a href=\"http://www.sitecore.net\" rel=\"noopener\">Sitecore</a>. The presentation was targeted towards developers who have no experience with Sitecore whatsoever. I had 30 minutes to explain and show what Sitecore is and that was the biggest challenge of the presentation, there is just too much to tell! Next time I’ll focus on one topic and go more in depth.</p><p>The HTML5 presentation is made with <a href=\"http://lab.hakim.se/reveal-js/#/\" rel=\"noopener\">reveal.js</a> and I’ve uploaded it to <a href=\"http://blogs.msdn.com/b/windowsazure/archive/2013/03/19/new-deploy-to-windows-azure-web-sites-from-dropbox.aspx\" rel=\"noopener\">Azure using Dropbox</a> (super easy!).</p><p><a href=\"http://marcduiker.azurewebsites.net/presentations/sitecore.html#/\" rel=\"noopener\">Have a look</a> and feel free to share and reuse it!</p>",
      "date_published": "2013-05-25T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/presentations-for-programmers/",
      "url": "https://marcduiker.dev/articles/presentations-for-programmers/",
      "title": "Presentations for Programmers",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/3.1.presentations_for_programmers-400w.webp 400w\" sizes=\"90vw\"><img src=\"/assets/images/3.1.presentations_for_programmers-400w.webp\" width=\"400\" height=\"258\" alt=\"Presentations for programmers\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"great-slides-great-speaker-=-great-presentation\"><a href=\"#great-slides-great-speaker-=-great-presentation\" class=\"heading-anchor\">Great Slides + Great Speaker = Great Presentation</a></h2><p>In the last few years I find myself doing more and more (technical) presentations. I have to admit I even start to like it. I absolutely admire great speakers like <a href=\"http://www.hanselman.com/\" rel=\"noopener\">Scott Hanselman</a> (Microsoft) and <a href=\"https://twitter.com/jerrong\" rel=\"noopener\">Tim Ward</a> (Sitecore/CluedIn). They speak with such passion about technology that I immediately want to use or do whatever they promote. Although my presentation skills are nowhere near the level of these speakers I do have a good grasp of what makes a great presentation. So before becoming a great speaker lets start with creating great slides.</p><h2 id=\"presentation-tools\"><a href=\"#presentation-tools\" class=\"heading-anchor\">Presentation Tools</a></h2><p>Regardless of the content, a tool is required for creating slides and doing presentations. For quite some time I’ve been on a quest to find a tool that fits all my needs.</p><h3 id=\"powerpoint\"><a href=\"#powerpoint\" class=\"heading-anchor\">Powerpoint</a></h3><p>As many I’ve started with Microsoft PowerPoint which is a very easy tool to create standard (and therefore boring) slides. I actually find it difficult to create interesting and captivating presentations with PowerPoint and that’s why I don’t use it.</p><h3 id=\"prezi\"><a href=\"#prezi\" class=\"heading-anchor\">Prezi</a></h3><p>Next I tried <a href=\"http://prezi.com/\" rel=\"noopener\">Prezi</a> for a while. Initially I was really blown away by this platform with its possibilities to zoom, translate, rotate etc. I still very much like these effects (when used in moderation) but I had some issues with applying custom styles and using the Prezi offline. The major downside for me is the lack of offline editing in all but the most expensive licenses. Another nuisance is the size of the presentations when used offline, these are huge compared to other platforms.</p><h3 id=\"html5\"><a href=\"#html5\" class=\"heading-anchor\">HTML5</a></h3><p>And now there is <a href=\"https://developer.mozilla.org/en/docs/HTML/HTML5\" rel=\"noopener\">HTML5</a>. And I love it. A lot of HTML5 presentation frameworks are emerging and currently I’m experimenting with <a href=\"https://revealjs.com/\" rel=\"noopener\">reveal.js</a> (written by <a href=\"http://hakim.se/\" rel=\"noopener\">Hakim El Hattab</a>). Really have a go at this one if you haven’t seen it yet!</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/3.2.reveal-400w.webp 400w\" sizes=\"90vw\"><img src=\"/assets/images/3.2.reveal-400w.webp\" width=\"400\" height=\"268\" alt=\"Reveal.js\" loading=\"lazy\" decoding=\"async\"></picture></p><p>An HTML5 presentation framework is just a collection of html, css and javascript files which you have complete control over (yay!). You can tweak the markup, styles and functions as desired. That immediately is also the downside for people who don’t have any web development skills (but they should stick to PowerPoint anyway 😉. You need to know a bit of html to get started (although reveal.js has an online editor called <a href=\"http://slides.com/\" rel=\"noopener\">slides.com</a>). The presentation can be used either online or offline, it all works the same, only an up-to-date browser is required (I prefer Chrome).</p><h2 id=\"how-its-made\"><a href=\"#how-its-made\" class=\"heading-anchor\">How it’s made</a></h2><p>I used <a href=\"http://www.sublimetext.com/2\" rel=\"noopener\">Sublime Text 2</a> to create the html (based on Hakim’s <a href=\"https://github.com/hakimel/reveal.js/blob/master/index.html\" rel=\"noopener\">template</a>) and a custom css theme (I named it <a href=\"http://marcduiker.azurewebsites.net/presentations/reveal.js/css/theme/programmer.css\" rel=\"noopener\">programmer.css</a>). I used a monospaced webfont called <a href=\"http://www.google.com/fonts/specimen/Inconsolata\" rel=\"noopener\">Inconsolata</a> in order to get an IDE look and feel. The color scheme is based on the Monokai theme. Feel free to re-use or adapt the programmer.css theme in your own reveal.js presentations.</p>",
      "date_published": "2013-04-19T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/visual-studio-snippets-for-unit-test-methods/",
      "url": "https://marcduiker.dev/articles/visual-studio-snippets-for-unit-test-methods/",
      "title": "Visual Studio Snippets for Unit Test Methods",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/2.1.unit_test_snippet-414w.webp 414w\" sizes=\"90vw\"><img src=\"/assets/images/2.1.unit_test_snippet-414w.webp\" width=\"414\" height=\"174\" alt=\"NUnit test snippet\" loading=\"lazy\" decoding=\"async\"></picture></p><h2 id=\"unit-test-method-naming\"><a href=\"#unit-test-method-naming\" class=\"heading-anchor\">Unit Test Method Naming</a></h2><p>As mentioned in a previous post, having a clear naming convention for unit test methods is important to keep tests readable and maintainable. Sticking to the convention might be difficult at first and require some discipline but is definitely worth it in the long run.</p><h3 id=\"visual-studio-snippets\"><a href=\"#visual-studio-snippets\" class=\"heading-anchor\">Visual Studio Snippets</a></h3><p>In order to make it easy for myself (and other developers) to use the <code>&lt;UnitOfWork&gt; _ &lt;StateUnderTest&gt; _ &lt;ExpectedBehaviour&gt;</code> naming convention, I’ve created two code snippets, one for NUnit and one for MSTest.<br>After importing the snippets (see below) you can use the them by typing:</p><p><code>nutest[TAB]</code> (for NUnit)</p><p>or</p><p><code>mstest[TAB]</code> (for MSTest)</p><p>The resulting code is displayed at the top of this post. The snippets use three placeholders (marked in yellow) which make up the method name using the suggested convention. In addition I’ve added Arrange, Act, Assert comments in order to help structuring the test.</p><h3 id=\"importing-snippets\"><a href=\"#importing-snippets\" class=\"heading-anchor\">Importing Snippets</a></h3><p>Once you’ve downloaded the snippet(s) open Visual Studio and go to <em>Tools &gt; Code Snippets Manager</em>.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/2.2.code_snippets_manager-440w.webp 440w, /assets/images/2.2.code_snippets_manager-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/2.2.code_snippets_manager-650w.webp\" width=\"650\" height=\"494\" alt=\"Code snippets manager\" loading=\"lazy\" decoding=\"async\"></picture></p><p>(If you open the Test folder you see two test related snippets already exist. These are <code>testc</code> for an MSTest class and <code>testm</code> for an MSTest method. The snippets do not use the suggested naming convention though.)</p><p>Click the <em>Import…</em> button, select the downloaded snippets and click <em>Open</em>. Then select the location to store the snippets (the Test folder seems obvious 😉.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/2.3.import_code_snippet-440w.webp 440w, /assets/images/2.3.import_code_snippet-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/2.3.import_code_snippet-650w.webp\" width=\"650\" height=\"533\" alt=\"Import code snippets\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Click <em>Finish</em> and the snippets are ready to be used. To verify that they are installed or to find out which shortcut they use open the <em>Code Snippets Manager</em> and select the snippet.</p><p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/2.4.check_code_snippet-440w.webp 440w, /assets/images/2.4.check_code_snippet-647w.webp 647w\" sizes=\"90vw\"><img src=\"/assets/images/2.4.check_code_snippet-647w.webp\" width=\"647\" height=\"487\" alt=\"Check code snippet\" loading=\"lazy\" decoding=\"async\"></picture></p><p>Reducing some boiler plate coding is always a good thing and I hope you’ll find these snippets convenient.</p><h2 id=\"resources\"><a href=\"#resources\" class=\"heading-anchor\">Resources</a></h2><ul class=\"list\"><li><a href=\"https://www.dropbox.com/s/86kpsnagd7ftgtc/nunit_testmethod.snippet\" rel=\"noopener\">NUnit code snippet</a></li><li><a href=\"https://www.dropbox.com/s/870fi15c4oik5qo/ms_testmethod.snippet\" rel=\"noopener\">MSTest code snippet</a></li></ul><p>P.S. Feel free to edit the snippets in order to change the shortcut or alter the code. They’re just XML files and there is some <a href=\"http://msdn.microsoft.com/en-us/library/ms165394.aspx\" rel=\"noopener\">MSDN documentation</a> you can work with.</p>",
      "date_published": "2013-04-11T00:00:00Z"
    }
    ,{
      "id": "https://marcduiker.dev/articles/the-importance-of-good-unit-tests-and-reviews/",
      "url": "https://marcduiker.dev/articles/the-importance-of-good-unit-tests-and-reviews/",
      "title": "The importance of good unit tests and test reviews",
      "content_html": "<p><picture slot=\"image\" class=\"flow\"><source type=\"image/webp\" srcset=\"/assets/images/1.goto-440w.webp 440w, /assets/images/1.goto-650w.webp 650w\" sizes=\"90vw\"><img src=\"/assets/images/1.goto-650w.webp\" width=\"650\" height=\"176\" alt=\"KXCD GOTO\" loading=\"lazy\" decoding=\"async\"></picture><br><em>Cover credit: <a href=\"https://xkcd.com/292/\" rel=\"noopener\">XKCD</a></em></p><p>I’m currently following an <a href=\"https://www.udemy.com/draft/14162/\" rel=\"noopener\">online TDD course</a> by <a href=\"http://osherove.com/\" rel=\"noopener\">Roy Osherove</a>. I’m about half way through and although I have quite a bit of experience writing unit tests and using test frameworks I’ve gained a lot of knowledge from the course already. Here are some highlights about good unit tests and test reviews.</p><h2 id=\"good-unit-tests\"><a href=\"#good-unit-tests\" class=\"heading-anchor\">Good Unit Tests</a></h2><p>Roy stresses the importance of the three pillars of a good unit test:</p><ol class=\"list\"><li>Readability</li><li>Maintainability</li><li>Trustworthiness</li></ol><p>If any of these are not taken into account during development developers are likely to drop unit tests all together because it will become a burden to use instead of an aid.</p><h2 id=\"unit-test-reviews\"><a href=\"#unit-test-reviews\" class=\"heading-anchor\">Unit Test Reviews</a></h2><p>In one of the lessons Roy talks about the importance of test reviews. Having a high code coverage doesn’t guarantee your code is good because you could be testing the wrong thing. Doing test reviews can help in ensuring the unit tests are correct. The unit tests themselves should be easy to read and understand to ensure maintainability.</p><h2 id=\"guidelines-for-writing-and-reviewing-tests\"><a href=\"#guidelines-for-writing-and-reviewing-tests\" class=\"heading-anchor\">Guidelines for Writing and Reviewing Tests</a></h2><ol class=\"list\"><li>Unit tests should be easy to locate in the solution. Are the unit tests in a separate project?</li><li>Readability is key in unit testing. Is there a clear naming convention?</li></ol><ul class=\"list\"><li><p>Naming the unit test projects:</p><p><code>&lt;ProjectUnderTest&gt;.UnitTests</code><br>(e.g. MyCompany.MyProduct.Business.UnitTests)</p></li><li><p>Naming the unit test classes:</p><p><code>&lt;ClassUnderTest&gt;Tests</code><br>(e.g. ShoppingBasketTests for the ShoppingBasket class)</p></li><li><p>Naming the unit test methods:</p><p><code>&lt;UnitOfWork&gt;_&lt;StateUnderTest&gt;_&lt;ExpectedBehavior&gt;</code><br>(e.g. CalculateDiscount_ForTenProducts_ReturnsDiscountOf5Percent see <a href=\"http://osherove.com/blog/2005/4/3/naming-standards-for-unit-tests.html\" rel=\"noopener\">Roy’s blog</a> for more examples)</p></li></ul><ol start=\"3\" class=\"list\"><li>Logic in unit tests should be avoided. Are there any if/else, switch, try/catch or loop statements in the test? Then refactor the test into separate tests.</li><li>Unit tests should be isolated and should always return the same result. Does the test have any dependencies to things beyond your control? (e.g. database, filesystem, DateTime, GUID)</li><li>A unit test should only test one thing. Are there several asserts in a unit test? Verify if you really need all those asserts. If you do, move them to individual unit tests.</li></ol><p>More guidelines on unit test reviews are given <a href=\"http://artofunittesting.com/unit-testing-review-guidelines/\" rel=\"noopener\">here</a>. In addition I can highly recommend reading <a href=\"http://www.manning.com/osherove/\" rel=\"noopener\">‘The Art Of Unit Testing’</a>. The second edition of this excellent book is expected in Q3 of this year.</p>",
      "date_published": "2013-04-09T00:00:00Z"
    }
    
  ]
}