<?xml version="1.0" encoding="utf-8" ?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>Tomáš Kohl</title>
	<subtitle />
	<link href="https://tomaskohl.com/code/atom.xml" rel="self" />
	<link href="https://tomaskohl.com" />
	<updated>2022-12-05T14:00:00Z</updated>
	<id>https://tomaskohl.com</id>
	<author>
		<name>Tomáš Kohl</name>
		<email>tomas@tomaskohl.com</email>
	</author>
	
	<entry>
		<title>How to make hourly billing work - if you must</title>
		<link
      href="https://tomaskohl.com/code/2022/how-to-make-hourly-billing-work-if-you-must/"
    />
		<updated>2022-12-05T14:00:00Z</updated>
		<id
    >https://tomaskohl.com/code/2022/how-to-make-hourly-billing-work-if-you-must/</id>
		<content
      type="html"
    >&lt;p&gt;Hourly billing is a widespread pricing scheme in software development and beyond. Inasmuch as it&#39;s easy to reason about in accounting terms, it creates unhealthy incentives on both sides of the contractual relationship.&lt;/p&gt;
&lt;p&gt;At its core, hourly billing is about buying and selling time spent doing &lt;em&gt;something&lt;/em&gt;, not producing any specific outcome or result.&lt;/p&gt;
&lt;p&gt;You pay your employees per unit of time, because you expect them to engage in a variety of tasks, and negotiating price over each incremental value would add too much friction.&lt;/p&gt;
&lt;p&gt;It&#39;s different with vendors and contractors. Ideally, you would have a well-defined job for them, one that they could conceivably value-price and create a win-win scenario.&lt;/p&gt;
&lt;p&gt;Value pricing requires that you can &lt;a href=&quot;https://jonathanstark.com/7-thoughts-on-value-pricing-for-software-projects&quot;&gt;create surplus&lt;/a&gt; for the customer and still keep a healthy profit margin.&lt;/p&gt;
&lt;p&gt;However, it is not a magic method that you could use everywhere and expect a profit for either party.&lt;/p&gt;
&lt;h2 id=&quot;hourly-billing%2C-if-you-must&quot; tabindex=&quot;-1&quot;&gt;Hourly billing, if you must &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/how-to-make-hourly-billing-work-if-you-must/#hourly-billing%2C-if-you-must&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Are there any scenarios where hourly billing is still preferable even with its fundamental flaws?&lt;/p&gt;
&lt;p&gt;I would argue that large enterprise change programs would fit.&lt;/p&gt;
&lt;p&gt;At their beginnings, there&#39;s often just a PowerPoint deck framing the CxO&#39;s vision, and a budget. No sane technology salesperson would value-price that.&lt;/p&gt;
&lt;p&gt;If you as a vendor had an especially good relationship with the company, you could do proper discovery, break the scope down, and value-price the iterations.&lt;/p&gt;
&lt;p&gt;Yet the budget is likely to remain the same, so why expend all the energy and not just go with the flow if there&#39;s nothing to be gained by being clever?&lt;/p&gt;
&lt;p&gt;The contract is likely going to be signed as an hourly affair. Despite being the best of all possible wrong pricing strategies to apply, it&#39;s likely to cause you, the vendor, to under-deliver, miss the deadlines, and become a major nuisance to the CxO in no time.&lt;/p&gt;
&lt;p&gt;How would you make hourly billing work there? Could you?&lt;/p&gt;
&lt;h3 id=&quot;the-four-riders-of-budgetocalypse&quot; tabindex=&quot;-1&quot;&gt;The four riders of budgetocalypse &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/how-to-make-hourly-billing-work-if-you-must/#the-four-riders-of-budgetocalypse&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here are a few contributing factors that cause enterprise programs to fail when run by hourly contractors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Incentives, incentives, incentives&lt;/strong&gt; - rather than assume intentional wrongdoing by the vendor, simply consider the underlying motivation that the contractor has. The only way to &amp;quot;scale&amp;quot; their business and increase &amp;quot;profit&amp;quot; is to bill as many hours as the customer can reasonably be expected to sign off on. The contractor can&#39;t be clever and do the work twice as fast without losing half of their income.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Oversight, or lack thereof&lt;/strong&gt; - a large-scale enterprise program may have dozens if not hundreds of contractors muddling through the backlog. Reviewing each timesheet individually is a non-starter. The company could do it if it had its employees at key positions throughout the teams, but do they? The only people motivated to cut vendors&#39; wings sit in the purchasing department and are no longer involved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agile&lt;/strong&gt; - Scrum is the default now instead of Waterfall, and that has further consequences. As &lt;a href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/&quot;&gt;Agile shifted the power balance to favor the developers&#39; interests&lt;/a&gt;, it made it completely legitimate for them to say, &amp;quot;this is as much as we are able to in the next two weeks, and not a line of code more.&amp;quot; Say what? Yes, you heard me, plus there&#39;s no appeals process. Add in hourly billing and brace yourself for timelines shifting into the future indefinitely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Corporate inertia&lt;/strong&gt; - it&#39;s a cliché to speak disparagingly about things taking forever in enterprise companies, and I won&#39;t. They have good reasons to be conservative: they have existing revenue to protect, quarterly earnings to report, and an army of employees to coordinate.&lt;/p&gt;
&lt;p&gt;The problem is that these large change programs need to operate at Internet speed to have a &lt;em&gt;chance&lt;/em&gt; of succeeding.&lt;/p&gt;
&lt;p&gt;When they don&#39;t, decisions come late (or never) while the clock is always running. Always. And while a big company can assume a lot of extra costs and still prevail in the end, paying its contractors by the hour with all of these factors combined is guaranteed to accomplish only one thing: &lt;strong&gt;spend the budget initially assigned to the program.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I may accomplish a few other things too, of course. Just without any guarantees.&lt;/p&gt;
&lt;h3 id=&quot;traits-of-successful-hourly-billing-arrangements&quot; tabindex=&quot;-1&quot;&gt;Traits of successful hourly billing arrangements &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/how-to-make-hourly-billing-work-if-you-must/#traits-of-successful-hourly-billing-arrangements&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&#39;ve shown you a few ways in which an enterprise change program can fail. Even more so when contractors are running the show. What about possible mitigations, are there any?&lt;/p&gt;
&lt;p&gt;It&#39;s helpful to start with the first principles. How to work with the vendor&#39;s motivation and try to align it with the desired outcome?&lt;/p&gt;
&lt;p&gt;Since we are stuck with hourly billing, we have to change the equation to limit its downside. Here are some that I&#39;ve witnessed make an impact.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Competition&lt;/strong&gt; - a free market does not need moral values to function. If there&#39;s enough competition, it will create and enforce them as if by magic. (Yes, there&#39;s more to it, but this isn&#39;t an economics textbook.)&lt;/p&gt;
&lt;p&gt;I would advise the CTO to be very intentional when hiring vendors to do the job and hire direct competitors. The point is not to promise the &amp;quot;winner&amp;quot; to take over the whole program. That would destroy the competitive environment and put the equation back where it was. The fact that they are not solely in charge is usually enough.&lt;/p&gt;
&lt;p&gt;I&#39;ve seen teams mixed from several vendors, and while there&#39;s some rivalry and chemistry to manage, they focus on the mission a lot more than teams manned exclusively by a single vendor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Oversight, for real&lt;/strong&gt; - do the reverse of the anti-pattern described in the preceding section and staff your people at key positions. There&#39;s no way around this. Nobody is as invested in the future of your company as your own employees, assuming you have a functional compensation scheme that encourages long-term thinking.&lt;/p&gt;
&lt;p&gt;When this is not possible, e.g., because of headcount limitations, consider hiring yet another consultancy whose job will be to oversee the program. Yes, everyone will hate them. So what! The point is to have someone on your side to keep the vendors in check. Remember, they are billing by the hour!&lt;/p&gt;
&lt;p&gt;Before you ask, no, I don&#39;t think these consultants should be billing hourly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lessening the Agile impedance mismatch&lt;/strong&gt; - as demonstrated, enterprise companies typically do not move at Internet speeds. The change program could be the best operating unit in the company, but if it stalls when interacting with the others, it can&#39;t deliver on its promise.&lt;/p&gt;
&lt;p&gt;The approach that I&#39;ve seen working best is to give the leaders as much autonomy as possible so that they don&#39;t have to consult (and ask for decisions) the parent entity more than once a quarter. Even better if the unit is a standalone entity, owned by the enterprise but operated independently.&lt;/p&gt;
&lt;p&gt;Since this topic deals more with how the program interfaces with the stakeholders rather than vendors, I will end here before going off-tangent. Solving it is crucial for the program&#39;s success regardless of how the vendors are paid. If hourly, then even more urgently so.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/how-to-make-hourly-billing-work-if-you-must/#conclusion&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For better or worse, hourly billing is here to stay.&lt;/p&gt;
&lt;p&gt;Even if broken, it can be the only pricing tool available, for example on large enterprise programs.&lt;/p&gt;
&lt;p&gt;Its inherent flaws and limitations cannot be fixed. You have to tame them, and you can.&lt;/p&gt;
&lt;p&gt;Having said all that, if you can avoid hourly billing as a client or vendor, I encourage you to do so. It&#39;s a tool of last resort that requires a lot of effort to manage. You could spend that effort creating something valuable instead.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Is Agile a welfare program for developers?</title>
		<link
      href="https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/"
    />
		<updated>2022-12-01T12:40:00Z</updated>
		<id
    >https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/</id>
		<content type="html">&lt;p&gt;How do you make developers comfortable?&lt;/p&gt;
&lt;p&gt;There are free lunches, game rooms, and massages. Consultants can help you craft the most comprehensive perks program in town.&lt;/p&gt;
&lt;p&gt;Or, you could implement Scrum.&lt;/p&gt;
&lt;p&gt;In the paragraphs that follow, I will argue that Agile has become an overfunded welfare program for developers.&lt;/p&gt;
&lt;p&gt;And since this is going to ruffle a few feathers, &lt;strong&gt;I will limit my indictment to Scrum projects in the context of enterprise software development.&lt;/strong&gt; This represents the bulk of my professional experience. Please read the text below through this lens.&lt;/p&gt;
&lt;p&gt;Also note that when I say Agile, I mean mostly Scrum.&lt;/p&gt;
&lt;h2 id=&quot;a-bit-of-history&quot; tabindex=&quot;-1&quot;&gt;A bit of history &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/#a-bit-of-history&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Back in the Waterfall era, project managers ruled supreme.&lt;/p&gt;
&lt;p&gt;They set deadlines, herded their flock of developers toward milestones, and had the CxOs on speed dial.&lt;/p&gt;
&lt;p&gt;Did the projects run over time and budget? They sure did! But you know what? We got real work done!&lt;/p&gt;
&lt;p&gt;One explanation is as follows. There&#39;s always tension between the client (the higher-ups) and the producer (developers). One side wants an X amount of work in a Y timeline while the other side can deliver a Z amount. And they struggle to assign values to these variables.&lt;/p&gt;
&lt;p&gt;In the Waterfall environment, this power dynamics favored the client&#39;s side. Overtime used to be common as deadlines had to be met. The developers, when asked to estimate, would argue in man-days, and the project manager would inevitably challenge them. A compromise would be forged, in which the developers would often draw the shorter straw.&lt;/p&gt;
&lt;p&gt;This was hard on developers. So, they invented Agile.&lt;/p&gt;
&lt;h2 id=&quot;agile%3A-lofty-and-idealistic-on-paper%2C-toothless-in-practice&quot; tabindex=&quot;-1&quot;&gt;Agile: lofty and idealistic on paper, toothless in practice &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/#agile%3A-lofty-and-idealistic-on-paper%2C-toothless-in-practice&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Recall that Agile was invented by developers, for developers.&lt;/p&gt;
&lt;p&gt;It was sold to the business folks differently, of course. As a way to deploy working software more quickly than the standard Waterfall allowed.&lt;/p&gt;
&lt;p&gt;And while I know nothing about the state of mind of the Agile inventors, I don&#39;t have to. Just looking at how the power dynamics have changed tells the story.&lt;/p&gt;
&lt;h3 id=&quot;case-in-point&quot; tabindex=&quot;-1&quot;&gt;Case in point &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/#case-in-point&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Consider any sufficiently ambitious program run by an enterprise so that it can finally enter the 21st century. Any &amp;quot;digitization&amp;quot; program would do.&lt;/p&gt;
&lt;p&gt;Bonus points if it&#39;s a &amp;quot;startup&amp;quot; launched and wholly owned by the enterprise.&lt;/p&gt;
&lt;p&gt;How is that program staffed? If the enterprise isn&#39;t a software house, chances are excellent that 90% of the staff are contractors. The company doesn&#39;t have enough developers in-house, and won&#39;t need them once the program finishes.&lt;/p&gt;
&lt;p&gt;Which, of course, it never does. Because guess what? The methodology sold to the higher-ups as a surefire way of joining the 21st century was Scrum.&lt;/p&gt;
&lt;p&gt;The enterprise has no agility in its DNA and the project runs as an isolated island embedded in the traditional hierarchy. The vision is handed down to contracted &amp;quot;product owners&amp;quot; who dutifully translate it to &amp;quot;user stories&amp;quot; and take it by the developers in the &amp;quot;refinement session.&amp;quot;&lt;/p&gt;
&lt;p&gt;There, the team evaluates each story and assigns it a point value on a scale between 1-13. &amp;quot;1&amp;quot; is like &amp;quot;yawn, I could do this now if I didn&#39;t have to be in this stupid meeting&amp;quot; and the &amp;quot;13&amp;quot; tells the PO, &amp;quot;eff you, break it down even further, there is too much unknown here.&amp;quot;&lt;/p&gt;
&lt;p&gt;What&#39;s the product owner to do? He could push back a little. But, the Scrum Master is there to ensure that The Process is adhered to, and in this process, the developers are always right. Always.&lt;/p&gt;
&lt;p&gt;The result is that developers develop at their own speed, and if they so desire, they can drag the backlog items over several Sprints and nobody will punish them.&lt;/p&gt;
&lt;p&gt;Now imagine you are paying someone to fix up your house, and they do this to you.&lt;/p&gt;
&lt;h3 id=&quot;the-counter-argument&quot; tabindex=&quot;-1&quot;&gt;The counter-argument &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/#the-counter-argument&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Agile has also produced numerous benefits, and not just for developers.&lt;/p&gt;
&lt;p&gt;It &amp;quot;normalized&amp;quot; relationships between developers and business folks. You don&#39;t need as many intermediaries as before (good-bye, business analysts!) and as developers got better at soft skills, they get to see the suits more often.&lt;/p&gt;
&lt;p&gt;The 24/7 deployment story can also be attributed to Agile. You could argue that the technical progress in the CI/CD tooling (and the cloud) made it possible, and I won&#39;t disagree. What I see in the Agile manifestos is the desire to deploy as frequently as possible (i.e., at the end of the Sprint in Scrum), and we do that now.&lt;/p&gt;
&lt;p&gt;Much better than once a quarter if you ask me.&lt;/p&gt;
&lt;h3 id=&quot;the-indictment&quot; tabindex=&quot;-1&quot;&gt;The indictment &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/#the-indictment&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Agile has changed the power dynamics and given developers too much power.&lt;/p&gt;
&lt;p&gt;My intent isn&#39;t to indict its inventors. I would like to inspire you to think about how we could course-correct instead.&lt;/p&gt;
&lt;p&gt;A situation where the development team can dictate its tempo is not healthy. That they alone define the deliverables for the next iteration is not healthy. It&#39;s bad for the people paying the bills, and it creates incentives for the developers to become lazy.&lt;/p&gt;
&lt;p&gt;Worse yet, it&#39;s a terrible fit for programs run by enterprises. Their internal processes have a huge impedance mismatch when confronted with Agile values, and so the implementation can only result in the most bastardized version possible.&lt;/p&gt;
&lt;h2 id=&quot;how-to-evolve-agile&quot; tabindex=&quot;-1&quot;&gt;How to evolve Agile &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/#how-to-evolve-agile&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In my practice, I never use Scrum and have &lt;a href=&quot;https://colladeo.com/blog/why-we-dont-use-scrum-backlogs/&quot;&gt;explained&lt;/a&gt; &lt;a href=&quot;https://colladeo.com/blog/why-we-dont-use-scrum-sprints/&quot;&gt;why&lt;/a&gt; (and &lt;a href=&quot;https://colladeo.com/blog/why-we-dont-use-scrum-ceremonies/&quot;&gt;what we do instead&lt;/a&gt;) on my agency blog. To be clear, the projects I run are nowhere near in size of the enterprise Scrum programs I talked about above.&lt;/p&gt;
&lt;p&gt;However we address this, the key thing is to &lt;strong&gt;correct the power dynamics.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Waterfall was bad because it made developers overworked cogs with no say about the process. It barely worked then and would be laughable now when you consider how long it took to ship anything.&lt;/p&gt;
&lt;p&gt;Agile can be better but mostly isn&#39;t, because its implementation swayed the balance too much in the developers&#39; favor.&lt;/p&gt;
&lt;p&gt;Where is the ideal middle ground, and is there one?&lt;/p&gt;
&lt;p&gt;I propose three areas for analysis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;adding more rigor to the planning&lt;/li&gt;
&lt;li&gt;adding &amp;quot;skin in the game&amp;quot;&lt;/li&gt;
&lt;li&gt;adding oversight&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;planning-with-purpose&quot; tabindex=&quot;-1&quot;&gt;Planning with purpose &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/#planning-with-purpose&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Agile favors &lt;a href=&quot;https://agilemanifesto.org/&quot;&gt;working software over comprehensive documentation&lt;/a&gt;. And yet, you couldn&#39;t deliver anything useful if you had no inputs and no customers.&lt;/p&gt;
&lt;p&gt;Having seen too many undercooked &amp;quot;user stories,&amp;quot; I would argue that good, rigorously analyzed documentation makes developers happier. Developers hate uncertainty.&lt;/p&gt;
&lt;p&gt;You don&#39;t have to go all the way back to the Waterfall days and try to describe the entire system up-front. I am in favor of iterative development. By all means, release something useful early and test it on customers.&lt;/p&gt;
&lt;p&gt;Consider documenting your iteration rigorously before development, however.&lt;/p&gt;
&lt;p&gt;By thinking through your use cases and documenting them, you can spot and resolve errors in your thinking early. Doing so on paper is cheaper than reworking already deployed code.&lt;/p&gt;
&lt;p&gt;This creates the first condition to correct the power balance. It gives developers more clarity about what they are about to do.&lt;/p&gt;
&lt;h3 id=&quot;adding-%22skin-in-the-game%22&quot; tabindex=&quot;-1&quot;&gt;Adding &amp;quot;skin in the game&amp;quot; &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/#adding-%22skin-in-the-game%22&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Agile is a little too optimistic about people&#39;s motivation and assumes everyone is equally interested in the outcome. I think adding carrots and sticks is necessary to make it so.&lt;/p&gt;
&lt;p&gt;Whichever way you do it, aligning compensation with performance is necessary.&lt;/p&gt;
&lt;p&gt;Before you object, yes, I am aware that measuring developer performance is not a trivial subject. It&#39;s also not specific to Agile, or any other methodology.&lt;/p&gt;
&lt;p&gt;My point is to highlight the importance of &amp;quot;skin in the game&amp;quot; for the success of any endeavor, including software development projects.&lt;/p&gt;
&lt;p&gt;Trading points may not suffice.&lt;/p&gt;
&lt;p&gt;Whatever compensation scheme you have in place, I would consider not exempting your Agile teams from it. When people are incentivized to perform at their very best, you create a second condition to fix the power balance.&lt;/p&gt;
&lt;h3 id=&quot;oversight&quot; tabindex=&quot;-1&quot;&gt;Oversight &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/#oversight&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This point is specific to enterprise projects that are largely staffed with contractors.&lt;/p&gt;
&lt;p&gt;While there&#39;s no inherent guarantee that your employees have the best interests of your company at heart, contractors usually care less.&lt;/p&gt;
&lt;p&gt;Giving contractors a blank check to run your project using Agile can lead to cost and deadline overruns that rival those of the Waterfall era.&lt;/p&gt;
&lt;p&gt;I witnessed large programs where the organization didn&#39;t have even its own CTO! Everyone besides a few key product people was contracted. In such an environment, there is no oversight and no accountability.&lt;/p&gt;
&lt;p&gt;Having core engineering staff is a must. They shouldn&#39;t be the &amp;quot;architecture astronauts&amp;quot; and prescribe solutions to the teams without having to deliver. On the contrary, I would put one or two internal employees on each team so that the technical decisions the team makes are aligned with the company&#39;s overall technical strategy and values.&lt;/p&gt;
&lt;p&gt;In a broader sense, oversight and controls should be built into the process regardless of who is doing the work (employees or contractors).&lt;/p&gt;
&lt;p&gt;Scrum has biweekly &amp;quot;reviews&amp;quot; and &amp;quot;retrospectives.&amp;quot; I would argue that two weeks is too short a period - it&#39;s a microcosm of tactical decisions.&lt;/p&gt;
&lt;p&gt;Your process should have checkpoints that review the process and outcomes on higher levels - maybe every 6 weeks to review how complete features are being (or not being) shipped, and once a quarter to check on major milestones. These are just examples - choose a frequency that best fits the project&#39;s timelines and your velocity.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/agile-is-a-welfare-program-for-developers/#summary&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I criticized Agile in light of how it&#39;s been implemented, especially in Scrum and specifically in the context of enterprise software development.&lt;/p&gt;
&lt;p&gt;In the process of writing, I realized I chose a topic that&#39;s quite big to chew on; I admit that. Many themes covered above deserve analysis on a much deeper level. I&#39;ve only touched the surface.&lt;/p&gt;
&lt;p&gt;The first version went off the rails as I switched my focus to the evils of hourly billing and unsuccessfully tried to tie it together with the deficiencies of Agile as I see them. I removed it and will publish a rant against hourly billing separately.&lt;/p&gt;
&lt;p&gt;All things considered, I am not against Agile in principle but think that we should evolve it pragmatically and that in the enterprise context, it needs more adult supervision than it often gets.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>You should probably just use AWS. Or Azure.</title>
		<link href="https://tomaskohl.com/code/2022/just-deploy-to-cloud/" />
		<updated>2022-11-25T14:20:00Z</updated>
		<id>https://tomaskohl.com/code/2022/just-deploy-to-cloud/</id>
		<content
      type="html"
    >&lt;p&gt;The cloud marketing departments spend gazillions of dollars to lure teams to &#39;migrate to the cloud&#39;.&lt;/p&gt;
&lt;p&gt;AWS is the default buying choice like IBM used to be.&lt;/p&gt;
&lt;p&gt;A contrarian opinion arguing against the cloud then naturally generates lots of waves.&lt;/p&gt;
&lt;p&gt;But, does it make sense to be a cloud contrarian?&lt;/p&gt;
&lt;h3 id=&quot;the-case-against-the-cloud&quot; tabindex=&quot;-1&quot;&gt;The case against the cloud &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/just-deploy-to-cloud/#the-case-against-the-cloud&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://world.hey.com/dhh/why-we-re-leaving-the-cloud-654b47e0&quot;&gt;Basecamp has recently announced&lt;/a&gt; that they&#39;ll be leaving the cloud.&lt;/p&gt;
&lt;p&gt;Their stated reasons are monetary (&amp;quot;paying over half a million dollars per year for database (RDS) and search (ES) services from Amazon&amp;quot;) and also based on values (&amp;quot;[it&#39;s] tragic that this decentralized wonder of the world is now largely operating on computers owned by a handful of mega corporations&amp;quot;).&lt;/p&gt;
&lt;p&gt;Basecamp operates on such a scale that owning a rack in a datacenter might yield some financial savings. Their devs can surely handle a few new sysadmin tasks.&lt;/p&gt;
&lt;p&gt;My friend Karl Sutt &lt;a href=&quot;https://www.karlsutt.com/articles/you-should-not-be-using-aws/&quot;&gt;argued against the cloud&lt;/a&gt; from a more general perspective. I will &lt;a href=&quot;https://www.steelmananything.com/topics/steelmanning/&quot;&gt;steelman&lt;/a&gt; his argument before offering my take.&lt;/p&gt;
&lt;p&gt;He based his critique on the following observations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cloud is expensive to operate and maintain&lt;/strong&gt; - not just in terms of the direct spend but also when you include the opportunity cost.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You are not Netflix&lt;/strong&gt;, so you don&#39;t need to replicate their setup for your non-planetscale app.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;There is too much choice of cloud services you can use&lt;/strong&gt;, and we developers can easily overcomplicate the design when a simple tech stack would do.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;cloud-is-too-expensive&quot; tabindex=&quot;-1&quot;&gt;Cloud is too expensive &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/just-deploy-to-cloud/#cloud-is-too-expensive&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;How easy is it to overpay for computers rented from Amazon? &lt;a href=&quot;https://www.reddit.com/r/ProgrammerHumor/comments/w4eo12/using_aws/&quot;&gt;Too easy&lt;/a&gt;:&lt;br /&gt;
&lt;img srcset=&quot;https://tomaskohl.com/code/assets/size/w320/2022/11/lmyrdzu7twc91.jpg 320w, https://tomaskohl.com/code/assets/size/w640/2022/11/lmyrdzu7twc91.jpg 640w, https://tomaskohl.com/code/assets/size/w960/2022/11/lmyrdzu7twc91.jpg 960w, https://tomaskohl.com/code/assets/size/w1920/2022/11/lmyrdzu7twc91.jpg 1920w&quot; src=&quot;https://tomaskohl.com/code/assets/2022/11/lmyrdzu7twc91.jpg&quot; alt=&quot;accidental $50,252 bill&quot; class=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Any sufficiently complex enterprise backend will at some point use services that will create very unpopular line items on the monthly invoice.&lt;/p&gt;
&lt;p&gt;And your backlog likely contains user stories such as &amp;quot;As a CTO, I want to slash my cloud spend by 50% to get the CFO off my ass.&amp;quot;&lt;/p&gt;
&lt;p&gt;In fact, the unavoidability of surprise AWS bills has created a cottage industry of AWS cost-reduction consultants. Given they exist, the cloud must be expensive, at least for some.&lt;/p&gt;
&lt;h4 id=&quot;you-don&#39;t-run-netflix&quot; tabindex=&quot;-1&quot;&gt;You don&#39;t run Netflix &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/just-deploy-to-cloud/#you-don&#39;t-run-netflix&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Your startup idea might become the new Netflix. Before your SaaS metrics prove that trajectory inevitable, you might not need microservices. Or multi-region redundancy. You&#39;d burn through your seed round before you&#39;d even have a chance to validate your idea.&lt;/p&gt;
&lt;h4 id=&quot;the-tragedy-of-the-hotel-breakfast-buffet&quot; tabindex=&quot;-1&quot;&gt;The tragedy of the hotel breakfast buffet &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/just-deploy-to-cloud/#the-tragedy-of-the-hotel-breakfast-buffet&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;One factor contributing to the overly complex (and expensive) cloud application architectures is the plethora of services you have at your disposal.&lt;/p&gt;
&lt;p&gt;The cloud is not just VMs. You have managed databases, message queues, CDNs, and a ton of other things.&lt;/p&gt;
&lt;p&gt;We developers are hyper-curious beings, which is a polite way of saying that we like trying out cool shit! AWS (and Azure and GCP) makes it way too easy.&lt;/p&gt;
&lt;p&gt;These managed services do enable computing on demand, and they also enable unnecessarily complex architectures.&lt;/p&gt;
&lt;p&gt;When there&#39;s little oversight over the cloud spend, we developers surely have a tendency to overeat!&lt;/p&gt;
&lt;h3 id=&quot;the-case-for-the-cloud&quot; tabindex=&quot;-1&quot;&gt;The case for the cloud &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/just-deploy-to-cloud/#the-case-for-the-cloud&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I think Karl&#39;s arguments are worth another look, if only because the alternative (running or renting bare metal servers) can also be costly, expensive, and has a non-zero opportunity cost, too.&lt;/p&gt;
&lt;p&gt;Since this question cannot be conclusively answered the same for all audiences, let&#39;s focus on the needs of an imaginary B2B SaaS startup that is bootstrapped.&lt;/p&gt;
&lt;p&gt;Being bootstrapped means having to watch over your budget with hawk&#39;s eyes. It&#39;s your money, after all.&lt;/p&gt;
&lt;p&gt;It means you are focused on finding (and proving) your product/market fit quickly, thus finding a way to profitability quickly.&lt;/p&gt;
&lt;p&gt;Assuming that you have one or two developers who are equally skilled in both cloud-native as well as &amp;quot;handcrafted&amp;quot; architecture, which way should you go - cloud-first, or rent (or buy) your own servers?&lt;/p&gt;
&lt;h4 id=&quot;comparing-direct-costs&quot; tabindex=&quot;-1&quot;&gt;Comparing direct costs &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/just-deploy-to-cloud/#comparing-direct-costs&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You could rent dedicated servers essentially for free.&lt;/p&gt;
&lt;p&gt;For instance, have a look at the &lt;a href=&quot;https://www.hetzner.com/sb&quot;&gt;server auctions at Hetzner&lt;/a&gt;. At the time of writing, I could get a box with Intel Core i7-6700 and 64 GB RAM for about €40 per month.&lt;/p&gt;
&lt;p&gt;That&#39;s less than I would spend on a company phone plan for one employee. As a business owner, such an expense is invisible to me.&lt;/p&gt;
&lt;p&gt;And yet, one or two servers in this configuration could power my app for a year or two without breaking a sweat.&lt;/p&gt;
&lt;p&gt;Try configuring a similarly specced machine in the cloud, and you&#39;re likely looking at a monthly bill that&#39;s upwards of $300.&lt;/p&gt;
&lt;p&gt;However, this comparison only makes sense if you&#39;re migrating apps from your datacenter to the cloud, and utilizing VMs is the first step in the process. You would not necessarily make the same decision when starting from scratch. More on that later.&lt;/p&gt;
&lt;p&gt;And yet, you are still probably going to run your app in the cloud for free.&lt;/p&gt;
&lt;p&gt;That&#39;s because the cloud vendors really want your business.&lt;/p&gt;
&lt;p&gt;Consider &lt;a href=&quot;https://www.microsoft.com/en-us/startups?rtc=1&quot;&gt;Microsoft for Startups&lt;/a&gt;. You can get up to $150,000 in Azure credits to develop and operate your app, not to mention Office and Visual Studio licenses and other incentives.&lt;/p&gt;
&lt;p&gt;Once you outgrow the program, you won&#39;t be paying list prices, either. Big customers have enterprise agreements and don&#39;t pay anything near the retail prices.&lt;/p&gt;
&lt;p&gt;That said, yes, you could at some point reach the same conclusions as Basecamp. I am not arguing that the cloud is always cheaper. When we limit our inquiry to the monthly cloud bills alone, it may not be. For instance, if your startup does not qualify for the incentives, your financial decisions will be different.&lt;/p&gt;
&lt;h4 id=&quot;comparing-opportunity-costs&quot; tabindex=&quot;-1&quot;&gt;Comparing opportunity costs &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/just-deploy-to-cloud/#comparing-opportunity-costs&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;My conclusions differ from Karl&#39;s as we consider the opportunity costs.&lt;/p&gt;
&lt;p&gt;Karl wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[T]here is productivity and opportunity cost from having to understand, deal with and work around the idiosyncracies of complex cloud platforms. This effort is much better spent on delivering value to users.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He proposes to put your customers&#39; needs above all else and to achieve that, do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Optimise for operational simplicity&lt;/strong&gt; - by choosing a deployment target that&#39;s easiest to maintain and operate. He mentions Heroku, Render or Fly as an alternative to AWS or GCP, which is debatable - Heroku is neither cheap nor easy to operate once you outgrow the cheapest dynos. I have no experience with the other two.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimise for internal simplicity&lt;/strong&gt; - I think he argues for the simplest possible architecture here, proposing to use &amp;quot;boring technologies&amp;quot; that are well understood.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus on delivering value to your users&lt;/strong&gt; - having this mindset should drive all technical decisions and you should evaluate every choice not on its technical merits alone but primarily on whether it adds the most value to the people using the product.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I won&#39;t argue against any of that since I share his beliefs. I differ in how I would realize them.&lt;/p&gt;
&lt;p&gt;Recall that in our example, we&#39;re trying to find a product/market fit for our bootstrapped startup. Delivering value to users is our job #1.&lt;/p&gt;
&lt;p&gt;Therefore, I would rather direct my team&#39;s energy toward customer development and shipping features quickly than toward building our own infra.&lt;/p&gt;
&lt;p&gt;Even as operating your own LAMP stack server, for example, is a well-documented proposition, there are adjacent concerns that you have to take into account, such as security, regulatory compliance (hello, GDPR!), reliability, disaster recovery, etc.&lt;/p&gt;
&lt;p&gt;Cloud vendors do a remarkable job of taking care of these mundane details (for a fee).&lt;/p&gt;
&lt;p&gt;You might find out, for instance, that serverless architecture is a good fit for your use case. Instead of running a full VM with all the overhead of following the security bulletins and applying patches on the regular, you could just deploy to Azure Functions or AWS Lambda, and only worry about your product&#39;s features.&lt;/p&gt;
&lt;p&gt;Indeed, my main case in favor of deploying to the cloud by default rests primarily on the extra abstraction layers that the cloud vendors offer on top of mundane VMs.&lt;/p&gt;
&lt;p&gt;It&#39;s not that you couldn&#39;t run &lt;a href=&quot;https://www.openfaas.com/&quot;&gt;your own serverless stack&lt;/a&gt; on your own hardware. Why would you spend your precious time on that, however, when your job #1 is finding and satisfying your first customers?&lt;/p&gt;
&lt;h3 id=&quot;finding-the-right-answer&quot; tabindex=&quot;-1&quot;&gt;Finding the right answer &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/just-deploy-to-cloud/#finding-the-right-answer&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Since this polemic runs on a very high level, I won&#39;t pretend to have answers for every scenario.&lt;/p&gt;
&lt;p&gt;There are indeed situations when running your own VM (or a bare-metal server) may be preferable to renting machines from Amazon (or Microsoft).&lt;/p&gt;
&lt;p&gt;To avoid the &amp;quot;overeating trap&amp;quot;, I would propose aligning the incentives of the technical team with the organization&#39;s goals and values.&lt;/p&gt;
&lt;p&gt;There are such things as budgets, and the CTO should enforce a rigid discipline with regard to cloud spending.&lt;/p&gt;
&lt;p&gt;I&#39;ve seen teams run completely amok having the right to power up an arbitrary number of cloud resources at will. That&#39;s not something a bootstrapped startup can afford. And a CFO at a Fortune 500 company would also come down knocking on doors and asking tough questions, eventually.&lt;/p&gt;
&lt;p&gt;When comparing your choices, I would propose the following metrics:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Time to market&lt;/strong&gt; - time is your most valuable resource besides your people. How can you ship stuff most quickly?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time to change&lt;/strong&gt; - you&#39;ll need to make a lot of decisions and make a lot of changes. Some of them will impact your code and require new resources, e.g., a new type of database. How quickly can you provision these? And once you don&#39;t need a particular resource, how easy is it to retire it and stop paying for it?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time to onboard&lt;/strong&gt; - how easy is it to onboard new developers on your stack? This is where &amp;quot;AWS is the new IBM&amp;quot; comes from. Pretty much every developer knows AWS these days, at least &amp;quot;kinda, sorta&amp;quot; (AWS is huge).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Figure these out, and only then compare the monthly spend. Chances are that it won&#39;t matter much in the first few years of your startup anyway.&lt;/p&gt;
&lt;p&gt;Your ability to attract paying customers and find a path to profitability will. Make sure your technical architecture is 100% aligned with this.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>How to publish a static website to Azure Static Web Apps</title>
		<link href="https://tomaskohl.com/code/2022/publish-static-web-to-azure/" />
		<updated>2022-07-03T15:20:00Z</updated>
		<id>https://tomaskohl.com/code/2022/publish-static-web-to-azure/</id>
		<content
      type="html"
    >&lt;h2 id=&quot;introduction&quot; tabindex=&quot;-1&quot;&gt;Introduction &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#introduction&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With a statically-built website, you have a variety of options when it comes to hosting. You can self-host on your own VPS, or use Netlify, Cloudflare... the world is your oyster. On Azure, you can try &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/static-web-apps/overview&quot;&gt;Azure Static Web Apps&lt;/a&gt;. Let&#39;s see how it works!&lt;/p&gt;
&lt;h2 id=&quot;what&#39;s-azure-static-web-apps&quot; tabindex=&quot;-1&quot;&gt;What&#39;s Azure Static Web Apps &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#what&#39;s-azure-static-web-apps&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The primary use case is to build and deploy your app from a code repository like Github. As soon as code is merged to &lt;code&gt;main&lt;/code&gt;, a task runs, and your changes are live. We did this in the Heroku heyday.&lt;/p&gt;
&lt;p&gt;You can use Azure Static Web Apps (SWA from now on) in various ways.&lt;/p&gt;
&lt;p&gt;The simplest would be to host plain HTML files with no build step at all.&lt;/p&gt;
&lt;p&gt;Or, build your HTML using a static-site generator (SSG from now on) like Hugo or Eleventy.&lt;/p&gt;
&lt;p&gt;Or, build a SPA app with React, Vue, etc.&lt;/p&gt;
&lt;p&gt;If your app is not really &amp;quot;static&amp;quot; and needs dynamic content, that&#39;s where Azure Functions comes in.&lt;/p&gt;
&lt;p&gt;We might explore that in a later article, but for today, we&#39;ll skip this. Suffice to say, Azure Functions is &amp;quot;married&amp;quot; to SWA and provides the &amp;quot;A&amp;quot; (=API) for running &lt;a href=&quot;https://www.cloudflare.com/learning/performance/what-is-jamstack/&quot;&gt;JAMstack&lt;/a&gt; apps on Azure.&lt;/p&gt;
&lt;h2 id=&quot;why-use-azure-static-web-apps-for-a-static-site&quot; tabindex=&quot;-1&quot;&gt;Why use Azure Static Web Apps for a static site &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#why-use-azure-static-web-apps-for-a-static-site&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You should consider SWA for your static site when the following features appeal to you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;automatic deployment from a source code repository&lt;/li&gt;
&lt;li&gt;an option to add dynamic capabilities (APIs) to your site in the future&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If neither is a concern and you prefer to deploy your site manually (with a CLI), you can always use Azure Blob Storage with Azure CDN and &lt;a href=&quot;https://www.aspnetmonsters.com/2020/05/2020-05-10-deploying-a-static-site-to-azure-using-the-az-cli/&quot;&gt;call it a day&lt;/a&gt;. SWA isn&#39;t set up for deployments that do not originate in a code repository (but &lt;a href=&quot;https://github.com/Azure/static-web-apps/issues/7#issuecomment-905381131&quot;&gt;there are workarounds&lt;/a&gt;, of course).&lt;/p&gt;
&lt;p&gt;Another good argument is that for non-planet-scale blogs and websites, you can deploy to Azure Static Web Apps for free. That includes a free, self-renewing SSL certificate (by DigiCert, not Letsencrypt as is customary elsewhere.)&lt;/p&gt;
&lt;p&gt;Should you need more than 100GB of traffic per month, you can &lt;a href=&quot;https://azure.microsoft.com/en-us/pricing/details/app-service/static/&quot;&gt;switch to a paid plan&lt;/a&gt; and get a global CDN as an additional perk (currently in preview).&lt;/p&gt;
&lt;h2 id=&quot;the-setup&quot; tabindex=&quot;-1&quot;&gt;The setup &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#the-setup&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/static-web-apps/getting-started?tabs=vanilla-javascript&quot;&gt;The official quick-start guide&lt;/a&gt; assumes you&#39;ve got nothing: no code, no domain, and no content.&lt;/p&gt;
&lt;p&gt;That makes it easier to explain the basics, I suppose. Most of you already have a website or a blog, however. I will tailor this guide to those migrating an existing site instead of starting from scratch.&lt;/p&gt;
&lt;h3 id=&quot;create-the-azure-resources&quot; tabindex=&quot;-1&quot;&gt;Create the Azure resources &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#create-the-azure-resources&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First, let&#39;s create the Azure resources and get your site up and running. We&#39;ll do this on the Azure Portal, but you can also use a CLI.&lt;/p&gt;
&lt;p&gt;Create a resource group (or pick an existing one), then proceed to create the SWA. There&#39;s a simple dialog where you pick the subscription and resource group, choose a name for your SWA, the hosting plan, location, and deployment source.&lt;/p&gt;
&lt;p&gt;&lt;img srcset=&quot;https://tomaskohl.com/code/assets/size/w320/2022/07/create-swa-in-azure.png 320w, https://tomaskohl.com/code/assets/size/w640/2022/07/create-swa-in-azure.png 640w, https://tomaskohl.com/code/assets/size/w960/2022/07/create-swa-in-azure.png 960w, https://tomaskohl.com/code/assets/size/w1920/2022/07/create-swa-in-azure.png 1920w&quot; src=&quot;https://tomaskohl.com/code/assets/2022/07/create-swa-in-azure.png&quot; alt=&quot;Create a static web app in Azure&quot; class=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; pick Github right away and go through the auth flow. It is not necessary, however, as you will see shortly.&lt;/p&gt;
&lt;p&gt;After Azure creates the site, navigate to it and copy the deployment token. You&#39;ll need it for the Github workflow. Also, take note of the auto-generated URL for your site. We will create a custom domain later, and we will need the URL for it.&lt;/p&gt;
&lt;p&gt;&lt;img srcset=&quot;https://tomaskohl.com/code/assets/size/w320/2022/07/details-of-swa-in-azure.png 320w, https://tomaskohl.com/code/assets/size/w640/2022/07/details-of-swa-in-azure.png 640w, https://tomaskohl.com/code/assets/size/w960/2022/07/details-of-swa-in-azure.png 960w, https://tomaskohl.com/code/assets/size/w1920/2022/07/details-of-swa-in-azure.png 1920w&quot; src=&quot;https://tomaskohl.com/code/assets/2022/07/details-of-swa-in-azure.png&quot; alt=&quot;Details of your static web app in Azure&quot; class=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;create-the-github-workflow&quot; tabindex=&quot;-1&quot;&gt;Create the Github workflow &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#create-the-github-workflow&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Following the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/static-web-apps/build-configuration?tabs=github-actions#build-and-deploy&quot;&gt;official build configuration guide&lt;/a&gt; will get you 90% there. Here, I am simplifying it even more under two assumptions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;since your site is 100% static, you don&#39;t need to worry about building the APIs,&lt;/li&gt;
&lt;li&gt;you don&#39;t need pull requests as you publish directly to &lt;code&gt;main&lt;/code&gt;. If that&#39;s not the case, you can follow the official guide to incorporate PRs into your workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are using the &lt;a href=&quot;https://github.com/Azure/static-web-apps-deploy&quot;&gt;official Github action&lt;/a&gt; for deployment to SWA.&lt;/p&gt;
&lt;p&gt;Create an empty file in the folder &lt;code&gt;.github/workflows&lt;/code&gt; of your repository and give it a name, e.g., &lt;code&gt;azure-static-web-apps-myblog.yml&lt;/code&gt;. Copy and paste the following, then edit &lt;code&gt;output_location&lt;/code&gt; and &lt;code&gt;app_build_command&lt;/code&gt; to match your config.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Azure Static Web Apps CI/CD

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; main &lt;span class=&quot;token comment&quot;&gt;# You can change it to whatever is your deployment branch&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;build_and_deploy_job&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build and Deploy Job
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v2
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build And Deploy
        &lt;span class=&quot;token key atrule&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; builddeploy
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Azure/static&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;web&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;apps&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;deploy@v1
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;azure_static_web_apps_api_token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $ &lt;span class=&quot;token comment&quot;&gt;# This is the deployment token for your SWA site&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;repo_token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $ &lt;span class=&quot;token comment&quot;&gt;# Used for GitHub integration&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;upload&quot;&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;app_location&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# This will typically be the repository root&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;api_location&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# No API here, therefore&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;skip_api_build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# skip API build&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;output_location&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_site&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Modify this to where your SSG places the built HTML - could be `dist`, `build`... check your config&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;app_build_command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;yarn build&#39;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Not necessary since the action will pick it up from &#39;package.json&#39; but I like it explicit&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before running the workflow, make sure that you put the secret &lt;code&gt;AZURE_STATIC_WEBAPP_DEPLOYMENT_TOKEN&lt;/code&gt; to your &amp;quot;Actions secrets&amp;quot; of your Github repository.&lt;/p&gt;
&lt;p&gt;That&#39;s it! Push to &lt;code&gt;main&lt;/code&gt; and observe that your site will be built and deployed to Azure.&lt;/p&gt;
&lt;h3 id=&quot;adding-a-custom-domain&quot; tabindex=&quot;-1&quot;&gt;Adding a custom domain &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#adding-a-custom-domain&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I am assuming you already have a custom domain for your site. If not (e.g., you run it on Github pages without a custom domain), you can buy one and use it on SWA.&lt;/p&gt;
&lt;p&gt;You have two options: &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/static-web-apps/custom-domain-external&quot;&gt;keep your existing nameservers&lt;/a&gt;, or &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/static-web-apps/custom-domain-azure-dns&quot;&gt;use Azure DNS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first option is often preferable as it minimizes the transition period from your old nameservers to Azure. However, it comes with a few caveats.&lt;/p&gt;
&lt;p&gt;When you want to run your site on an apex domain (&lt;code&gt;mydomain.com&lt;/code&gt;), your DNS provider must support the &lt;code&gt;ALIAS&lt;/code&gt; record since SWA doesn&#39;t give you a fixed IP address.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;CNAME&lt;/code&gt; would also work, but some providers won&#39;t let you point the apex domain to an URL. If you run your site on a subdomain (e.g., &lt;code&gt;blog.mydomain.com&lt;/code&gt;), you can simply create a &lt;code&gt;CNAME&lt;/code&gt; for &lt;code&gt;blog&lt;/code&gt; and point it to the URL Azure generated for your site.&lt;/p&gt;
&lt;p&gt;I wanted to use an apex domain, and my provider did not meet these requirements, so I moved my DNS records to Azure.&lt;/p&gt;
&lt;p&gt;The following section only applies to you if you are in the same boat. If you can set up the correct DNS records at your current provider, do that instead!&lt;/p&gt;
&lt;h4 id=&quot;setting-up-azure-dns&quot; tabindex=&quot;-1&quot;&gt;Setting up Azure DNS &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#setting-up-azure-dns&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Create a new resource of type &lt;code&gt;DNS Zone&lt;/code&gt; in your resource group.&lt;/p&gt;
&lt;p&gt;&lt;img srcset=&quot;https://tomaskohl.com/code/assets/size/w320/2022/07/create-dns-zone-in-azure.png 320w, https://tomaskohl.com/code/assets/size/w640/2022/07/create-dns-zone-in-azure.png 640w, https://tomaskohl.com/code/assets/size/w960/2022/07/create-dns-zone-in-azure.png 960w, https://tomaskohl.com/code/assets/size/w1920/2022/07/create-dns-zone-in-azure.png 1920w&quot; src=&quot;https://tomaskohl.com/code/assets/2022/07/create-dns-zone-in-azure.png&quot; alt=&quot;Create a DNS zone in Azure&quot; class=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then, navigate to your current DNS provider and copy all records to your new Azure DNS zone. You will do so via the &amp;quot;+ Record set&amp;quot; menu.&lt;/p&gt;
&lt;p&gt;&lt;img srcset=&quot;https://tomaskohl.com/code/assets/size/w320/2022/07/create-dns-records-in-azure-dns-zone.png 320w, https://tomaskohl.com/code/assets/size/w640/2022/07/create-dns-records-in-azure-dns-zone.png 640w, https://tomaskohl.com/code/assets/size/w960/2022/07/create-dns-records-in-azure-dns-zone.png 960w, https://tomaskohl.com/code/assets/size/w1920/2022/07/create-dns-records-in-azure-dns-zone.png 1920w&quot; src=&quot;https://tomaskohl.com/code/assets/2022/07/create-dns-records-in-azure-dns-zone.png&quot; alt=&quot;Create DNS records in a DNS zone in Azure&quot; class=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You&#39;ll want to make sure (REALLY make sure) that you&#39;ve copied ALL of the records you need. You don&#39;t want to leave out the MX records, for example, and stop receiving emails... I guess 🤷‍♂️ &lt;em&gt;(HELLO, Inbox Zero!)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Before continuing, switch your nameservers to Azure.&lt;/p&gt;
&lt;p&gt;I wish I could postpone this step, but Azure wouldn&#39;t let me add the custom domain until I did so. I&#39;d imagine it could have something to do with them trying to create an SSL certificate for you, but I could be wrong. If you know, please let us know in the comments below!&lt;/p&gt;
&lt;h4 id=&quot;connecting-the-custom-domain&quot; tabindex=&quot;-1&quot;&gt;Connecting the custom domain &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#connecting-the-custom-domain&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;If you&#39;re using Azure DNS, it&#39;s now simpler as Azure will do the domain validation for you. Otherwise, you&#39;ll create a few &lt;code&gt;TXT&lt;/code&gt; and &lt;code&gt;CNAME&lt;/code&gt; records at your DNS provider.&lt;/p&gt;
&lt;p&gt;Navigate to your SWA and select Custom domains. Then, add one (for subdomains like &lt;code&gt;blog.mydomain.com&lt;/code&gt;) or two (for apex domains that include the &lt;code&gt;www&lt;/code&gt; subdomain).&lt;/p&gt;
&lt;p&gt;&lt;img srcset=&quot;https://tomaskohl.com/code/assets/size/w320/2022/07/add-custom-domain-via-azure-dns.png 320w, https://tomaskohl.com/code/assets/size/w640/2022/07/add-custom-domain-via-azure-dns.png 640w, https://tomaskohl.com/code/assets/size/w960/2022/07/add-custom-domain-via-azure-dns.png 960w, https://tomaskohl.com/code/assets/size/w1920/2022/07/add-custom-domain-via-azure-dns.png 1920w&quot; src=&quot;https://tomaskohl.com/code/assets/2022/07/add-custom-domain-via-azure-dns.png&quot; alt=&quot;Connect custom domains to SWA in Azure&quot; class=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With a domain hosted in Azure DNS, you&#39;ll be done sooner than if Azure has to validate your domain. When I did that (before ultimately transferring my nameservers to Azure), it took a long time - 15, maybe 20 minutes - before Azure picked up on the verification DNS records.&lt;/p&gt;
&lt;p&gt;When all is done and green, I would pick the default domain. For a subdomain like &lt;code&gt;blog.mydomain.com&lt;/code&gt;, that&#39;s an easy choice. When using an apex domain and &lt;code&gt;www&lt;/code&gt;, choose one or the other depending on which one you prefer. Azure will then redirect requests to your default domain, which is what you want.&lt;/p&gt;
&lt;h4 id=&quot;verifying-your-setup&quot; tabindex=&quot;-1&quot;&gt;Verifying your setup &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#verifying-your-setup&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;If you have migrated your nameservers to Azure, this will take &lt;strong&gt;hours&lt;/strong&gt; until the DNS changes propagate.&lt;/p&gt;
&lt;p&gt;A good way to test is to look at the SSL certificate. If you&#39;ve used Letsencrypt before, your site is now protected by DigiCert. As long as you see the old certificate, you&#39;re still looking at the &amp;quot;old&amp;quot; site.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/publish-static-web-to-azure/#conclusion&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have done this migration for my &lt;a href=&quot;https://colladeo.com/&quot;&gt;agency website&lt;/a&gt;, and I managed it in a single afternoon.&lt;/p&gt;
&lt;p&gt;It&#39;s not difficult at all; just a few gotchas are waiting for you down the road.&lt;/p&gt;
&lt;p&gt;For me, the main benefit is the automated deployment once I push to &lt;code&gt;main&lt;/code&gt;. I preview all changes locally, and I don&#39;t have an editor who would go through my copy and improve it.&lt;/p&gt;
&lt;p&gt;If that were the case (e.g., a hired copywriter would submit a PR to me), I would add the pull requests to the mix. I have not yet tested this, but Azure supposedly generates preview environments for your PRs!&lt;/p&gt;
&lt;p&gt;That&#39;s something I might look into very soon, as well as adding the Azure Functions into the mix.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title
    >How to set up automated deployment of a Linux function app on Azure Functions Consumption plan</title>
		<link
      href="https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/"
    />
		<updated>2022-06-08T15:15:00Z</updated>
		<id
    >https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/</id>
		<content
      type="html"
    >&lt;h2 id=&quot;introduction&quot; tabindex=&quot;-1&quot;&gt;Introduction &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#introduction&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://azure.microsoft.com/en-us/services/functions/&quot;&gt;Azure Functions&lt;/a&gt; is a feature-rich platform-as-a-service (PaaS) offering of the Azure cloud, competing with AWS Lambda in the serverless space. It is particularly well suited for event-driven systems and, by its design, encourages a modular, decoupled system architecture, e.g., by way of micro-services.&lt;/p&gt;
&lt;h3 id=&quot;about-serverless&quot; tabindex=&quot;-1&quot;&gt;About serverless &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#about-serverless&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &amp;quot;serverless&amp;quot; moniker is both appealing and misleading.&lt;/p&gt;
&lt;p&gt;The appeal is straightforward: only care about your code. The platform takes care of the runtime and dependencies, among other things.&lt;/p&gt;
&lt;p&gt;The misdirection lies in the moniker itself. For one, serverless should not be equated with zero thinking about infrastructure and deployment. It also presents no threat to the careers of DevOps professionals.&lt;/p&gt;
&lt;p&gt;Think about serverless the way you would think about renting a serviced apartment. It comes with extra features you won&#39;t find in a standard apartment, e.g., there&#39;s weekly cleaning and no extra charge for a plumber fixing the pipes.&lt;/p&gt;
&lt;p&gt;Alas, there are also things you can&#39;t do. A serverless platform puts hard limits on how you can configure the system, and when there&#39;s a feature that&#39;s missing, well, you are out of luck - no &lt;code&gt;sudo apt install $package&lt;/code&gt; for you.&lt;/p&gt;
&lt;p&gt;For a great many applications, the benefits outweigh the costs.&lt;/p&gt;
&lt;p&gt;And to achieve maximum benefits, it&#39;s worth investing in your CI/CD pipeline. After setting up the automated deployment of your serverless app, you aren&#39;t really thinking about servers at all.&lt;/p&gt;
&lt;h3 id=&quot;why-you-should-read-this-tutorial&quot; tabindex=&quot;-1&quot;&gt;Why you should read this tutorial &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#why-you-should-read-this-tutorial&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&#39;ve gone through multiple code samples and tutorials, looking for guidance on configuring the CI/CD pipeline for an app running on Azure Functions. There are many, many out there - &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview&quot;&gt;Microsoft Docs&lt;/a&gt; are excellent and are usually my first place to inquire.&lt;/p&gt;
&lt;p&gt;That said, given the various tiers and plans Azure Functions offers, not every tutorial works out of the box on every plan.&lt;/p&gt;
&lt;p&gt;The most prominent entrance to the Azure Functions world is through the Consumption plan since you are not being charged any flat fees regardless of usage but only for when your code actually runs. It is indeed this plan that I chose for my experiments and ended up a little bruised along the way.&lt;/p&gt;
&lt;p&gt;This tutorial/explainer is tailored to fellow hackers setting up their apps on the Linux Consumption plan. It is in many ways generic in that the principles can be applied to the other Azure Functions plans, though. I highlight any solutions specific to the Consumption plan.&lt;/p&gt;
&lt;h3 id=&quot;when-(not)-to-worry-about-this&quot; tabindex=&quot;-1&quot;&gt;When (not) to worry about this &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#when-(not)-to-worry-about-this&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I would not worry about this in the initial stages of a small-ish project where you are the only developer.&lt;/p&gt;
&lt;p&gt;You can always create resources manually in Azure or via Azure CLI, and deployment from VS Code or Visual Studio is just a few clicks away.&lt;/p&gt;
&lt;p&gt;The time to think about automated deployment is when another developer joins the team or when the infrastructure becomes stable, and you get tired of manually deploying several times a day, whichever comes first.&lt;/p&gt;
&lt;p&gt;By &amp;quot;stable infrastructure&amp;quot;, I mean: you have figured out which database you need, you have created other supplemental resources like the Service Bus namespace, and now you spend most of your effort on writing the application code.&lt;/p&gt;
&lt;h3 id=&quot;pre-requisites&quot; tabindex=&quot;-1&quot;&gt;Pre-requisites &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#pre-requisites&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I assume some experience developing on the Azure Functions platform and familiarity with general Azure topics. This article covers setting up a deployment pipeline on Github; if you host your code elsewhere, e.g., on Gitlab, you&#39;ll have to adjust the Github-specific bits.&lt;/p&gt;
&lt;p&gt;Furthermore, this article addresses a specific scenario for a Functions app running on the Consumption plan on Linux. If your app runs on Windows or on a Premium or App Service plan, then some of the quirks and limitations addressed below do not apply to you.&lt;/p&gt;
&lt;h3 id=&quot;the-take-away-promise&quot; tabindex=&quot;-1&quot;&gt;The take-away promise &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#the-take-away-promise&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After reading this tutorial, you will learn about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;how to understand the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview&quot;&gt;ARM templates&lt;/a&gt; and how to use them to describe the resources you want to deploy to Azure declaratively&lt;/li&gt;
&lt;li&gt;how to deploy the resources from ARM templates with the &lt;a href=&quot;https://docs.microsoft.com/en-us/cli/azure/get-started-with-azure-cli&quot;&gt;Azure CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;how to use &lt;a href=&quot;https://docs.github.com/en/actions/using-workflows/about-workflows&quot;&gt;Github workflows&lt;/a&gt; to trigger deployments based on repository events and on-demand&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My sole goal is to empower you to set up your own deployment pipeline for your very own project or app in Azure Functions.&lt;/p&gt;
&lt;h3 id=&quot;about-this-example&quot; tabindex=&quot;-1&quot;&gt;About this example &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#about-this-example&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We&#39;ll go beyond &amp;quot;Hello world&amp;quot; and look at the automation pipeline of &lt;a href=&quot;https://github.com/teekay/jcomments&quot;&gt;JComments&lt;/a&gt;, my Open Source project aimed at publishers using the JAM stack.&lt;/p&gt;
&lt;p&gt;It provides commenting functionality to static websites. One API is used at build time to bake the existing comments into the generated HTML. The same API is then called by JavaScript to fetch any comments posted after the page was built. Another API processes incoming comments from web pages.&lt;/p&gt;
&lt;p&gt;The project is written in TypeScript and runs on Node.js, which is one of the languages supported by Azure Functions. It uses a Postgres database, and when deployed to Azure, it can take advantage of the managed Azure Postgres service. A few event-driven use cases utilize a message queue, which in Azure means using the Service Bus.&lt;/p&gt;
&lt;p&gt;The architecture is pretty vanilla, so the chances are that you&#39;ll be able to derive learnings from this article to apply to your own app and its setup on Azure.&lt;/p&gt;
&lt;h2 id=&quot;the-setup&quot; tabindex=&quot;-1&quot;&gt;The setup &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#the-setup&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The code lives on Github, which also provides the CI/CD pipeline.&lt;/p&gt;
&lt;p&gt;You&#39;ll find the workflows under &lt;a href=&quot;https://github.com/teekay/jcomments/tree/master/.github/workflows&quot;&gt;&lt;code&gt;.github/workflows&lt;/code&gt;&lt;/a&gt;, and we&#39;ll take a look at them at the end of this tutorial.&lt;/p&gt;
&lt;p&gt;The ARM templates and scripts are hosted in &lt;a href=&quot;https://github.com/teekay/jcomments/tree/master/infra/azure&quot;&gt;&lt;code&gt;infra/azure&lt;/code&gt;&lt;/a&gt;. We will cover these first.&lt;/p&gt;
&lt;p&gt;It&#39;s common to set up your resources manually at first - I did that, and likely you did, too. Creating an ARM template manually from scratch would be an exercise only a masochist would enjoy. Even taking an exported template and making a few tweaks would usually take a few commits and failed deploys to get right.&lt;/p&gt;
&lt;p&gt;The first step is to export the ARM templates of your resources so that we can edit and generalize them.&lt;/p&gt;
&lt;h3 id=&quot;preparing-arm-templates-for-automated-deployment&quot; tabindex=&quot;-1&quot;&gt;Preparing ARM templates for automated deployment &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#preparing-arm-templates-for-automated-deployment&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Navigate to the resource and look up the link &amp;quot;Export template&amp;quot; under the menu item &amp;quot;Automation,&amp;quot; just above &amp;quot;Support + troubleshooting.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;img srcset=&quot;https://tomaskohl.com/code/assets/size/w320/2022/06/1-export-arm-template.png 320w, https://tomaskohl.com/code/assets/size/w640/2022/06/1-export-arm-template.png 640w, https://tomaskohl.com/code/assets/size/w960/2022/06/1-export-arm-template.png 960w, https://tomaskohl.com/code/assets/size/w1920/2022/06/1-export-arm-template.png 1920w&quot; src=&quot;https://tomaskohl.com/code/assets/2022/06/1-export-arm-template.png&quot; alt=&quot;Export ARM template&quot; class=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Click it and wait until Azure generates the template, then save it somewhere where you can open it locally with your favorite editor.&lt;/p&gt;
&lt;p&gt;The exported template will have a lot of specific bits you would want to parametrize if you were to use it for deployment to another resource group or subscription. In particular, you&#39;ll want to think about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;naming conventions, which are help organize resources by a common factor such as environment or location,&lt;/li&gt;
&lt;li&gt;configuration parameters, esp. if they could be different from one environment to another&lt;/li&gt;
&lt;li&gt;dependencies on other resources, which can go either way (consumed or provided or both)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&#39;s have a look at the ARM template of an App Service Plan when exported:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;contentVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;serverfarms_ASP_jamcommentsstaging_9cbf_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;defaultValue&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ASP-jamcommentsstaging-9cbf&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;String&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;variables&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.Web/serverfarms&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2021-03-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;serverfarms_ASP_jamcommentsstaging_9cbf_name&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;East US&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;B1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;tier&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Basic&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;B1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;family&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;B&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;capacity&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;perSiteScaling&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;elasticScaleEnabled&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;maximumElasticWorkerCount&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;isSpot&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;reserved&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;isXenon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;hyperV&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;targetWorkerCount&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;targetWorkerSizeId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;zoneRedundant&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What you would want here is not to have the resource name hard-coded and (optionally) to extract the SKU and properties to &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/parameters&quot;&gt;parameters&lt;/a&gt; that can change independently of the template, e.g., from one environment to another.&lt;/p&gt;
&lt;p&gt;Including a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/parameter-files&quot;&gt;parameters template&lt;/a&gt; is a convenient way of achieving this. You can also supply parameters from the command-line for any values you don&#39;t want to be persisted in your code repository, e.g., secrets.&lt;/p&gt;
&lt;h4 id=&quot;a-brief-interlude-about-the-naming-convention&quot; tabindex=&quot;-1&quot;&gt;A brief interlude about the naming convention &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#a-brief-interlude-about-the-naming-convention&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You&#39;ll want to think about the naming conventions to use and take into account aspects of your infrastructure that won&#39;t change as well as those that might.&lt;/p&gt;
&lt;p&gt;For example, you may want to make the location of the app configurable and to use multiple environments that are deployed separately and may have different sizing profiles. In our example, we&#39;ll have &lt;code&gt;staging&lt;/code&gt; and &lt;code&gt;production&lt;/code&gt;. As you will see, we&#39;ll be able to add another very easily, perhaps &lt;code&gt;development&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The naming convention I&#39;ll use here will use &lt;code&gt;jcomm-&lt;/code&gt; as the leading prefix for everything. The next segment will be the environment shorthand, &lt;code&gt;staging-&lt;/code&gt; for the staging environment and &lt;code&gt;prod-&lt;/code&gt; for production.&lt;/p&gt;
&lt;p&gt;The final segment if the naming prefix will be the location. Here, we&#39;ll be using &amp;quot;East US&amp;quot; and shorten it to &lt;code&gt;eus-&lt;/code&gt; for the prefix.&lt;/p&gt;
&lt;p&gt;For each resource we&#39;ll pick a suitable abbreviation. For an App Service Plan, let&#39;s use &lt;code&gt;asp&lt;/code&gt;. That&#39;s also the only part that the template will decide for itself, so to speak.&lt;/p&gt;
&lt;p&gt;The big advantage of doing this is that you will immediately know what&#39;s what at first glance when you explore a resource group in the Azure portal. It&#39;s a decision you&#39;d make at the beginning and then use consistently.&lt;/p&gt;
&lt;p&gt;Pick whatever pattern that suits your needs best. Perhaps the location isn&#39;t important to you, or it won&#39;t ever change. Whatever. The point is to identify the commonalities as well as differences.&lt;/p&gt;
&lt;h4 id=&quot;the-set-up%2C-continued&quot; tabindex=&quot;-1&quot;&gt;The set up, continued &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#the-set-up%2C-continued&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To accommodate our requirements, the first part of the template will morph into this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;contentVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;String&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;variables&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;componentName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-asp&#39;)]&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What about the &lt;code&gt;sku&lt;/code&gt; and &lt;code&gt;properties&lt;/code&gt;? Let&#39;s extract them to the parameters file and save it to a file. Later, we may want to merge the function app and its App Service plan to a single deployment template and do so for their parameters as well.&lt;/p&gt;
&lt;p&gt;The parameters file then reads as follows:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;contentVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;servicePlanSku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Y1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;tier&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Dynamic&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Y1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;family&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Y&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;capacity&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;servicePlanProperties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;perSiteScaling&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;elasticScaleEnabled&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;maximumElasticWorkerCount&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;isSpot&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;reserved&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;isXenon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;hyperV&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;targetWorkerCount&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;targetWorkerSizeId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;zoneRedundant&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that set up separately, the final version of the App Service plan template will look like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;contentVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;String&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;String&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;servicePlanSku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;servicePlanProperties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;variables&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;componentName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-asp&#39;)]&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.Web/serverfarms&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2021-03-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[variables(&#39;componentName&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;location&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;servicePlanSku&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;servicePlanProperties&#39;)]&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the name of the resource we&#39;re deploying has become a variable that takes the provided &lt;code&gt;prefix&lt;/code&gt; from parameters and adds its own abbreviation to it. The resource is deployed from its ARM template and one or more parameter files together, as you will see below.&lt;/p&gt;
&lt;p&gt;We would use the same logic for all the other parts of our stack.&lt;/p&gt;
&lt;h3 id=&quot;the-infrastructure&quot; tabindex=&quot;-1&quot;&gt;The infrastructure &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#the-infrastructure&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We will start with the infrastructure - Key Vault, Postgres, and Service Bus.&lt;/p&gt;
&lt;h4 id=&quot;azure-key-vault&quot; tabindex=&quot;-1&quot;&gt;Azure Key Vault &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#azure-key-vault&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;An important part is the Azure Key Vault, which holds various secrets such as the database passwords and API keys to third-party services.&lt;/p&gt;
&lt;p&gt;The Key Vault is both a provider and a consumer, at least in the sense that other parts of the stack will put their secrets into it when deployed.&lt;/p&gt;
&lt;p&gt;From now on, I won&#39;t present the original ARM template and only highlight the final, edited template. I also won&#39;t include the parameters - again, the method to extract and then apply the parameters is the same. My recommendation is that your method to create resources should be consistent and follow the same logic so that adding a new resource type is as easy as possible.&lt;/p&gt;
&lt;p&gt;The Key Vault template looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;contentVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;keyvault sku&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;mailgunApiKey&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;securestring&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;mailgunSender&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;mailgunDomain&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;variables&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;componentName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-kv&#39;)]&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.KeyVault/vaults&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2021-11-01-preview&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[variables(&#39;componentName&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;location&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;sku&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;tenantId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[subscription().tenantId]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;accessPolicies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;enabledForDeployment&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;enabledForDiskEncryption&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;enabledForTemplateDeployment&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;enableSoftDelete&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.KeyVault/vaults/secrets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2019-09-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;componentName&#39;), &#39;/mailgunApiKey&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;[resourceId(&#39;Microsoft.KeyVault/vaults&#39;, variables(&#39;componentName&#39;))]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;mailgunApiKey&#39;)]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.KeyVault/vaults/secrets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2019-09-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;componentName&#39;), &#39;/mailgunSender&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;[resourceId(&#39;Microsoft.KeyVault/vaults&#39;, variables(&#39;componentName&#39;))]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;mailgunSender&#39;)]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.KeyVault/vaults/secrets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2019-09-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;componentName&#39;), &#39;/mailgunDomain&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;[resourceId(&#39;Microsoft.KeyVault/vaults&#39;, variables(&#39;componentName&#39;))]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;mailgunDomain&#39;)]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the configuration property &lt;code&gt;&amp;quot;enabledForTemplateDeployment&amp;quot;: true&lt;/code&gt; - this will let other components write secrets into the Key Vault.&lt;/p&gt;
&lt;p&gt;Also, notice that the deployment template expects three parameters specifying the &amp;quot;secrets&amp;quot; the app will consume to interact with the &lt;a href=&quot;https://www.mailgun.com/&quot;&gt;Mailgun&lt;/a&gt; API. Granted, only the API key is really a secret, and it&#39;s debatable whether the sender (an e-mail address) or the domain needs to be in the Key Vault. You&#39;ll make your own choices depending on how you want to handle similar considerations.&lt;/p&gt;
&lt;p&gt;If you are wondering where the Mailgun secrets come from, you&#39;ll see soon enough.&lt;/p&gt;
&lt;h4 id=&quot;the-service-bus-namespace&quot; tabindex=&quot;-1&quot;&gt;The Service Bus namespace &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#the-service-bus-namespace&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Adding a Service Bus namespace to your project is surprisingly easy.&lt;/p&gt;
&lt;p&gt;Pick a SKU depending on your usage - if you can get by with just Queues, you can go with the Basic pricing tier; to use Topics and Subscriptions, you&#39;ll need Standard.&lt;/p&gt;
&lt;p&gt;We&#39;ll put the connection string to the Key Vault so that the apps can access it to send and receive messages.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;contentVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.ServiceBus/namespaces sku&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;variables&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;componentName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-svcbus&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;keyVaultName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-kv&#39;)]&quot;&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.ServiceBus/namespaces&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2021-11-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[variables(&#39;componentName&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;location&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;sku&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;zoneRedundant&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.KeyVault/vaults/secrets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2019-09-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;keyvaultName&#39;), &#39;/serviceBusPrimaryConnectionString&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;Microsoft.ServiceBus/namespaces/&#39;, variables(&#39;componentName&#39;))]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[listKeys(resourceId(&#39;Microsoft.ServiceBus/namespaces/authorizationRules&#39;, variables(&#39;componentName&#39;), &#39;RootManageSharedAccessKey&#39;) ,&#39;2017-04-01&#39;).primaryConnectionString]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;postgres-flexible-server&quot; tabindex=&quot;-1&quot;&gt;Postgres Flexible Server &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#postgres-flexible-server&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Lastly, let&#39;s deploy the Postgres database.&lt;/p&gt;
&lt;p&gt;There&#39;s nothing fundamentally new here. I included it in the tutorial since I&#39;ve had to tackle an unexpected problem when specifying multiple configuration parameters at the same time, and I reckon you might want to do that, too.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;contentVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;storageSizeGB&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;int&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;administratorLoginPassword&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;securestring&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;databases&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;array&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;variables&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;componentName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-pg&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;administratorLogin&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jcstgadmin&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;keyVaultName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-kv&#39;)]&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.DBforPostgreSQL/flexibleServers&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2021-06-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[variables(&#39;componentName&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;location&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;sku&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;13&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;administratorLogin&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[variables(&#39;administratorLogin&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;administratorLoginPassword&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;administratorLoginPassword&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;availabilityZone&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;storage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;storageSizeGB&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;storageSizeGB&#39;)]&quot;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;backup&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;backupRetentionDays&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;geoRedundantBackup&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Disabled&quot;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;network&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;highAvailability&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;mode&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Disabled&quot;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;maintenanceWindow&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;customWindow&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Disabled&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;dayOfWeek&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;startHour&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;startMinute&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.DBforPostgreSQL/flexibleServers/firewallRules&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2021-06-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;componentName&#39;), &#39;/AllowAllAzureServicesAndResourcesWithinAzureIps&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;[resourceId(&#39;Microsoft.DBforPostgreSQL/flexibleServers&#39;, variables(&#39;componentName&#39;))]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;startIpAddress&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;endIpAddress&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.DBforPostgreSQL/flexibleServers/configurations&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2021-06-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;componentName&#39;), &#39;/azure.extensions&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;[resourceId(&#39;Microsoft.DBforPostgreSQL/flexibleServers&#39;, variables(&#39;componentName&#39;))]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;[resourceId(&#39;Microsoft.DBforPostgreSQL/flexibleServers/firewallRules&#39;, variables(&#39;componentName&#39;), &#39;AllowAllAzureServicesAndResourcesWithinAzureIps&#39;)]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PGCRYPTO&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user-override&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;copy&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;databasecopy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[length(parameters(&#39;databases&#39;))]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.DBforPostgreSQL/flexibleServers/databases&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiversion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2021-06-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;componentName&#39;), &#39;/&#39;, parameters(&#39;databases&#39;)[copyIndex()].name)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;[resourceId(&#39;Microsoft.DBforPostgreSQL/flexibleServers&#39;, variables(&#39;componentName&#39;))]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;charset&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;UTF8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;collation&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;en_US.utf8&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.KeyVault/vaults/secrets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2019-09-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;keyVaultName&#39;), &#39;/pgHost&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;componentName&#39;), &#39;.postgres.database.azure.com&#39;)]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.KeyVault/vaults/secrets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2019-09-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;keyVaultName&#39;), &#39;/pgAdminUsername&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[variables(&#39;administratorLogin&#39;)]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.KeyVault/vaults/secrets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2019-09-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;keyVaultName&#39;), &#39;/pgAdminPassword&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;administratorLoginPassword&#39;)]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem I had was with the resource types &lt;code&gt;Microsoft.DBforPostgreSQL/flexibleServers/firewallRules&lt;/code&gt; and &lt;code&gt;Microsoft.DBforPostgreSQL/flexibleServers/configurations&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When both of them only had a dependency on &lt;code&gt;[resourceId(&#39;Microsoft.DBforPostgreSQL/flexibleServers&#39;, variables(&#39;componentName&#39;))]&lt;/code&gt;, the firewall rules were deployed while the attempt to white-list the &lt;code&gt;pgcrypto&lt;/code&gt; extension failed with a response code &lt;code&gt;Conflict&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What gives?&lt;/p&gt;
&lt;p&gt;I suspect that in that scenario, the orchestrator tries to write to the same configuration file, and either one of the attempts can fail since the other one may not be finished yet.&lt;/p&gt;
&lt;p&gt;I solved it by making the second configuration depend on the first one being finished, but I think it&#39;s a hacky workaround at best.&lt;/p&gt;
&lt;p&gt;If you were to add multiple custom configurations to my database server, which you very well might want to do, you would end up with a growing mess of dependencies that the orchestrator should figure out on its own, in my opinion anyway.&lt;/p&gt;
&lt;p&gt;That, or I&#39;ve overlooked a better way of handling this. Please let me know in the comments if you know it!&lt;/p&gt;
&lt;p&gt;Another noteworthy feature I want to highlight is how the ARM template for the Postgres database puts its own secrets into the Key Vault. Unlike with the function app, which we&#39;ll cover shortly, we haven&#39;t given the Postgres any rights to read or write to the Key Vault here, and yet it can do so.&lt;/p&gt;
&lt;p&gt;This is a little unexpected but very practical since the function app will need the Postgres credentials to talk to it.&lt;/p&gt;
&lt;h3 id=&quot;the-function-app&quot; tabindex=&quot;-1&quot;&gt;The function app &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#the-function-app&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Azure Functions lets you choose from several pricing plans that come with different performance and scaling characteristics.&lt;/p&gt;
&lt;p&gt;The Consumption plan is great for getting started as you only pay for the actual usage, and there is a generous free monthly grant. It may also work great in production for apps that do not handle continuous sustained traffic.&lt;/p&gt;
&lt;p&gt;The key drawback of the Consumption plan on Linux is that it does not support deployment from a ZIP package directly. Rather, you have to put the ZIP package somewhere, e.g., into a container in Azure Blob Storage, and provide the URL to the package.&lt;/p&gt;
&lt;p&gt;This is &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/run-functions-from-deployment-package&quot;&gt;clearly stated in the docs&lt;/a&gt;, but not every tutorial you will find on the Internet takes this into account. For example, if you thought you could just use the &lt;a href=&quot;https://github.com/Azure/functions-action&quot;&gt;Azure Functions Github action&lt;/a&gt; to deploy your code, it won&#39;t work for Linux apps on the Consumption plan. The pipeline will finish green, but your code won&#39;t deploy.&lt;/p&gt;
&lt;p&gt;The correct template to build on is &lt;a href=&quot;https://github.com/Azure-Samples/function-app-arm-templates/tree/main/function-app-linux-consumption&quot;&gt;this one&lt;/a&gt; referenced in &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-infrastructure-as-code?tabs=linux#create-a-function-app&quot;&gt;Azure docs&lt;/a&gt;. But you&#39;ll need a little more code to make it work.&lt;/p&gt;
&lt;p&gt;My solution is based on &lt;a href=&quot;https://www.markheath.net/post/run-from-package&quot;&gt;this fantastic article by Mark Heath&lt;/a&gt;, which got me to 80% of where I needed to be.&lt;/p&gt;
&lt;p&gt;Below is the rather long ARM template for the function app, which also contains the App Service plan resource. You&#39;ll see that it does not specify the package URL - we don&#39;t have it yet.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;contentVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;fullDeployment&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bool&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;servicePlanSku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sku of Microsoft.Web/serverfarms&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;servicePlanProperties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;properties of Microsoft.Web/serverfarms&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;variables&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;componentName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-fapp&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;servicePlanName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-fasp&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;keyVaultName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-kv&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;appSettings&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;APPINSIGHTS_INSTRUMENTATIONKEY&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;@Microsoft.KeyVault(VaultName=&#39;, variables(&#39;keyVaultName&#39;), &#39;;SecretName=appInsightsInstrumentationKey)&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;AzureWebJobsStorage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;@Microsoft.KeyVault(VaultName=&#39;, variables(&#39;keyVaultName&#39;), &#39;;SecretName=storageAccountConnectionString-jamcomments-fapp&#39;, &#39;)&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;FUNCTIONS_EXTENSION_VERSION&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;~4&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;FUNCTIONS_WORKER_RUNTIME&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;MAILGUN_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;@Microsoft.KeyVault(VaultName=&#39;, variables(&#39;keyVaultName&#39;), &#39;;SecretName=mailgunApiKey)&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;MAILGUN_DOMAIN&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;@Microsoft.KeyVault(VaultName=&#39;, variables(&#39;keyVaultName&#39;), &#39;;SecretName=mailgunDomain)&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;MAILGUN_SENDER&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;@Microsoft.KeyVault(VaultName=&#39;, variables(&#39;keyVaultName&#39;), &#39;;SecretName=mailgunSender)&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;PGDATABASE&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jamcomments&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;PGSSLMODE&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;require&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;PGHOST&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;@Microsoft.KeyVault(VaultName=&#39;, variables(&#39;keyVaultName&#39;), &#39;;SecretName=pgHost)&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;PGUSER&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;@Microsoft.KeyVault(VaultName=&#39;, variables(&#39;keyVaultName&#39;), &#39;;SecretName=pgAdminUsername)&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;PGPASSWORD&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;@Microsoft.KeyVault(VaultName=&#39;, variables(&#39;keyVaultName&#39;), &#39;;SecretName=pgAdminPassword)&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;SERVICEBUS_CONNECTION&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;@Microsoft.KeyVault(VaultName=&#39;, variables(&#39;keyVaultName&#39;), &#39;;SecretName=serviceBusPrimaryConnectionString)&#39;)]&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.Web/serverfarms&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[variables(&#39;servicePlanName&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2020-09-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;location&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;servicePlanProperties&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;servicePlanSku&#39;)]&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;condition&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;fullDeployment&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;[concat(&#39;Microsoft.Web/serverfarms/&#39;, variables(&#39;servicePlanName&#39;))]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.Web/sites&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2021-03-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[variables(&#39;componentName&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;location&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;functionapp,linux&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;identity&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SystemAssigned&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;enabled&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;serverFarmId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[resourceId(&#39;Microsoft.Web/serverfarms&#39;, variables(&#39;servicePlanName&#39;))]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;siteConfig&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;numberOfWorkers&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;linuxFxVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Node|14&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;alwaysOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;functionAppScaleLimit&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;minimumElasticInstanceCount&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;clientAffinityEnabled&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;keyVaultReferenceIdentity&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SystemAssigned&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;condition&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[parameters(&#39;fullDeployment&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;appSettings&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2020-09-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-keyVaultAccessPolicy&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;[resourceId(&#39;Microsoft.Web/sites&#39;, variables(&#39;componentName&#39;))]&quot;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[variables(&#39;appSettings&#39;)]&quot;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.Resources/deployments&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2020-10-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(parameters(&#39;prefix&#39;), &#39;-keyVaultAccessPolicy&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;[resourceId(&#39;Microsoft.Web/sites&#39;, variables(&#39;componentName&#39;))]&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;mode&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Incremental&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;contentVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token property&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.KeyVault/vaults/accessPolicies&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[concat(variables(&#39;keyVaultName&#39;), &#39;/add&#39;)]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;token property&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2019-09-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                            &lt;span class=&quot;token property&quot;&gt;&quot;accessPolicies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                                &lt;span class=&quot;token property&quot;&gt;&quot;tenantId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[reference(resourceId(&#39;Microsoft.Web/sites&#39;, variables(&#39;componentName&#39;)), &#39;2018-02-01&#39;, &#39;Full&#39;).identity.tenantId]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                &lt;span class=&quot;token property&quot;&gt;&quot;objectId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[reference(resourceId(&#39;Microsoft.Web/sites&#39;, variables(&#39;componentName&#39;)), &#39;2018-02-01&#39;, &#39;Full&#39;).identity.principalId]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                &lt;span class=&quot;token property&quot;&gt;&quot;permissions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                                &lt;span class=&quot;token property&quot;&gt;&quot;keys&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                                    &lt;span class=&quot;token string&quot;&gt;&quot;get&quot;&lt;/span&gt;
                                &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                &lt;span class=&quot;token property&quot;&gt;&quot;secrets&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                                    &lt;span class=&quot;token string&quot;&gt;&quot;get&quot;&lt;/span&gt;
                                &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
                                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On a side note, giving the function app an identity, which is this bit:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;identity&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SystemAssigned&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;makes it possible to give it permissions to talk to the Key Vault and get a few secrets from it and make them available to the application code via environment variables. The permission is granted by the resource &lt;code&gt;&amp;quot;type&amp;quot;: &amp;quot;Microsoft.KeyVault/vaults/accessPolicies&amp;quot;&lt;/code&gt;, and the secrets are inserted into the environment variables in the &lt;code&gt;appSettings&lt;/code&gt; variables.&lt;/p&gt;
&lt;p&gt;With the layout of the ARM templates being presented and explained, we can move on to the scripts that deploy them.&lt;/p&gt;
&lt;h2 id=&quot;the-deployment-process&quot; tabindex=&quot;-1&quot;&gt;The deployment process &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#the-deployment-process&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The infrastructure is deployed separately from the function app. The idea is to run the infrastructure deployment only when required so that most of the time, only the function app is deployed because only the application code changes frequently.&lt;/p&gt;
&lt;p&gt;I won&#39;t go over the infrastructure deployment. A curious reader can peruse the &lt;a href=&quot;https://github.com/teekay/jcomments/blob/master/infra/azure/platform/scripts/create-infra.sh&quot;&gt;&lt;code&gt;infra/azure/platform/scripts/create-infra.sh&lt;/code&gt;&lt;/a&gt; Bash script.&lt;/p&gt;
&lt;h3 id=&quot;the-deployment-scripts&quot; tabindex=&quot;-1&quot;&gt;The deployment scripts &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#the-deployment-scripts&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The function app has two scripts: one that creates the function app and its related resources (storage account, app service plan, Service Bus topics), and another that deploys the application code.&lt;/p&gt;
&lt;p&gt;First, let&#39;s have a look at how the function app and its specific resources are deployed (&lt;a href=&quot;https://github.com/teekay/jcomments/blob/master/infra/azure/apps/api/scripts/create-funcapp.sh&quot;&gt;&lt;code&gt;infra/azure/apps/api/scripts/create-funcapp.sh&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${environment&lt;span class=&quot;token operator&quot;&gt;:?&lt;/span&gt;variable not set or empty}&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${fullDeployment&lt;span class=&quot;token operator&quot;&gt;:?&lt;/span&gt;variable not set or empty}&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$CI&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Running on CI&quot;&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;AZURE_CORE_OUTPUT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;none
&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Not running on CI&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Starting function app deployment&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$environment&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
 staging&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token assign-left variable&quot;&gt;paramFolder&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;staging&quot;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 prod&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token assign-left variable&quot;&gt;paramFolder&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;prod&quot;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 *&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;unknown environment: &lt;span class=&quot;token variable&quot;&gt;$environment&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt;
   &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;esac&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;templates&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;infra/azure/apps/api/templates&quot;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;commonParams&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$templates&lt;/span&gt;/params/common&quot;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;envParams&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$templates&lt;/span&gt;/params/&lt;span class=&quot;token variable&quot;&gt;$paramFolder&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;East US&quot;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;locationSymbol&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;eus&quot;&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jcomm-&lt;span class=&quot;token variable&quot;&gt;$environment&lt;/span&gt;-&lt;span class=&quot;token variable&quot;&gt;$locationSymbol&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;rgp&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$prefix&lt;/span&gt;-rgp&quot;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Creating resource group &lt;span class=&quot;token variable&quot;&gt;$rgp&lt;/span&gt;&quot;&lt;/span&gt;
az group create &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$rgp&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--location&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$location&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Creating storage account&quot;&lt;/span&gt;
az deployment group create &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$prefix&lt;/span&gt;-storage-account&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$rgp&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$templates&lt;/span&gt;&quot;&lt;/span&gt;/storage.json &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$commonParams&lt;/span&gt;&quot;&lt;/span&gt;/storage-account-parameters.json &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$envParams&lt;/span&gt;&quot;&lt;/span&gt;/storage-account-parameters.json &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token assign-left variable&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$prefix&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token assign-left variable&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$environment&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token assign-left variable&quot;&gt;locationSymbol&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$locationSymbol&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token assign-left variable&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$location&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Creating service bus topics&quot;&lt;/span&gt;
az deployment group create &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$prefix&lt;/span&gt;-sb-topics&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$rgp&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$templates&lt;/span&gt;&quot;&lt;/span&gt;/svcbus-topics.json &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$commonParams&lt;/span&gt;&quot;&lt;/span&gt;/svcbus-topics-parameters.json &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$envParams&lt;/span&gt;&quot;&lt;/span&gt;/svcbus-topics-parameters.json &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token assign-left variable&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$prefix&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Creating function app&quot;&lt;/span&gt;
az deployment group create &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$prefix&lt;/span&gt;-fapp&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$rgp&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$templates&lt;/span&gt;&quot;&lt;/span&gt;/funcapp.json &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$commonParams&lt;/span&gt;&quot;&lt;/span&gt;/funcapp-parameters.json &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$envParams&lt;/span&gt;&quot;&lt;/span&gt;/funcapp-parameters.json &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token assign-left variable&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$prefix&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token assign-left variable&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$location&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;token assign-left variable&quot;&gt;fullDeployment&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$fullDeployment&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Finished creating function app&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, a few key parameters that the templates expect are defined here, such as the location of the resources and the prefix. The script needs two environment variables, &lt;code&gt;environment&lt;/code&gt; and &lt;code&gt;fullDeployment&lt;/code&gt;, supplied by the Github runtime triggering the workflow run.&lt;/p&gt;
&lt;p&gt;Next, here is how the application code is deployed (&lt;a href=&quot;https://github.com/teekay/jcomments/blob/master/infra/azure/apps/api/scripts/deploy-funcapp.sh&quot;&gt;&lt;code&gt;infra/azure/apps/api/scripts/deploy-funcapp.sh&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${environment&lt;span class=&quot;token operator&quot;&gt;:?&lt;/span&gt;variable not set or empty}&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Get the storage connection string&quot;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;AZURE_STORAGE_CONNECTION_STRING&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;az storage account show-connection-string &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jcomm-&lt;span class=&quot;token variable&quot;&gt;${environment}&lt;/span&gt;-eus-rgp&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jc&lt;span class=&quot;token variable&quot;&gt;${environment}&lt;/span&gt;eusstgacc&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--query&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;connectionString&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; tsv&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;jamcomments-funcapp-releases
&lt;span class=&quot;token assign-left variable&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;date&lt;/span&gt; +&lt;span class=&quot;token string&quot;&gt;&quot;%Y%M%d%H%M%N&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;blobName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;functionapp&lt;span class=&quot;token variable&quot;&gt;${timestamp}&lt;/span&gt;.zip&quot;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Create storage container&quot;&lt;/span&gt;
az storage container create &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$container&lt;/span&gt;&quot;&lt;/span&gt; --public-access off --connection-string &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$AZURE_STORAGE_CONNECTION_STRING&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Zip the app&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;zip&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$blobName&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; -x@.funcignore &lt;span class=&quot;token parameter variable&quot;&gt;-x&lt;/span&gt; .funcignore

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Upload &lt;span class=&quot;token variable&quot;&gt;${blobName}&lt;/span&gt; to storage&quot;&lt;/span&gt;
az storage blob upload &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$container&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$blobName&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$blobName&lt;/span&gt;&quot;&lt;/span&gt;  --connection-string &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$AZURE_STORAGE_CONNECTION_STRING&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;blobUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;az storage blob url &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$container&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$blobName&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; tsv --connection-string &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$AZURE_STORAGE_CONNECTION_STRING&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;expiry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--date&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;5 years&#39;&lt;/span&gt; +&lt;span class=&quot;token string&quot;&gt;&quot;%Y-%m-%dT%H:%M:%SZ&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Get the SAS for the ZIP package&quot;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;sas&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;az storage blob generate-sas &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$container&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$blobName&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--permissions&lt;/span&gt; r &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; tsv &lt;span class=&quot;token parameter variable&quot;&gt;--expiry&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$expiry&lt;/span&gt;&quot;&lt;/span&gt; --connection-string &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$AZURE_STORAGE_CONNECTION_STRING&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;packageUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${blobUrl}&lt;/span&gt;?&lt;span class=&quot;token variable&quot;&gt;${sas}&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Set the app settings to run from package URL&quot;&lt;/span&gt;
az webapp config appsettings &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jcomm-&lt;span class=&quot;token variable&quot;&gt;${environment}&lt;/span&gt;-eus-fapp&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jcomm-&lt;span class=&quot;token variable&quot;&gt;${environment}&lt;/span&gt;-eus-rgp&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--settings&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;WEBSITE_RUN_FROM_PACKAGE=&lt;span class=&quot;token variable&quot;&gt;$packageUrl&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quite a few steps to put the ones and zeroes to work, ain&#39;t it?&lt;/p&gt;
&lt;p&gt;I think the inline comments paint a clear picture but to recap, you have to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;create a private-access container in the Blob storage&lt;/li&gt;
&lt;li&gt;zip the application files&lt;/li&gt;
&lt;li&gt;upload the ZIP archive to the container&lt;/li&gt;
&lt;li&gt;ask Azure for the URL to the archive&lt;/li&gt;
&lt;li&gt;ask Azure to give you a read-only &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview&quot;&gt;shared access signature (SAS)&lt;/a&gt; to the archive&lt;/li&gt;
&lt;li&gt;construct the full URL&lt;/li&gt;
&lt;li&gt;add a configuration to the function app to run from the zip archive we just uploaded&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To be clear, this solution is specific to Linux function apps running on the Consumption plan. If you are on any other plan, you can just use the &lt;a href=&quot;https://github.com/Azure/functions-action&quot;&gt;Azure Functions Github action&lt;/a&gt; and let it do the ZIP deployment for you.&lt;/p&gt;
&lt;p&gt;I don&#39;t fully understand where this limitation comes from. If pressed, I would hypothesise that the Linux function apps on the Consumption plan do not have any dedicated storage (other than the Storage Account that&#39;s required for all Functions to operate - but then, why doesn&#39;t the runtime use it for the ZIP package hosting?) unlike all the other plans. In reality, I simply don&#39;t know, and the docs do not reveal the reasons.&lt;/p&gt;
&lt;p&gt;With that said, let&#39;s see how this all is tied together in a Github workflow.&lt;/p&gt;
&lt;h3 id=&quot;the-github-workflow&quot; tabindex=&quot;-1&quot;&gt;The Github workflow &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#the-github-workflow&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The workflow featured below (see &lt;a href=&quot;https://github.com/teekay/jcomments/blob/master/.github/workflows/azure-jam-comments-api.yml&quot;&gt;&lt;code&gt;.github/workflows/azure-jam-comments-api.yml&lt;/code&gt;&lt;/a&gt; in the JComments repo) prepares the environment variables, runs the build and test actions, then calls the scripts (described in the previous section) to create the infrastructure in Azure and deploy the code.&lt;/p&gt;
&lt;p&gt;The workflow can be triggered manually (see &lt;code&gt;workflow_dispatch&lt;/code&gt;) or automatically by pushes to &lt;code&gt;dev&lt;/code&gt; and &lt;code&gt;main&lt;/code&gt; branches.&lt;/p&gt;
&lt;p&gt;The very first time the workflow should run, or put differently, the very first time a given resource group and its contents are created in Azure, it should run manually with the &lt;code&gt;fullDeployment&lt;/code&gt; flag set to &lt;code&gt;true&lt;/code&gt; otherwise not all resources will have been created.&lt;/p&gt;
&lt;p&gt;For our use case, it&#39;s important that we have the &lt;code&gt;zip&lt;/code&gt; binary available. The action &lt;code&gt;montudor/action-zip@v1&lt;/code&gt; takes care of that.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deploy JComments API to Azure Functions

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;workflow_dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Name of environment to deploy (staging, prod)&#39;&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;staging&#39;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;fullDeployment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Whether to do a full deployment or not&#39;&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;false&#39;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; dev
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; master
    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;infra/azure/apps/api/**&#39;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.github/workflows/azure-jam-comments-api.yml&#39;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;AzureCommentsApiGet/**&#39;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;AzureCommentsApiPost/**&#39;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;AzureCommentsApiNotify/**&#39;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;sql/**&#39;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;src/**&#39;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;test/**&#39;&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;NODE_VERSION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;14.x&#39;&lt;/span&gt;                     &lt;span class=&quot;token comment&quot;&gt;# set this to the node version to use (supports 8.x, 10.x, 12.x)&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;fullDeployment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; github.event.inputs.fullDeployment &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &#39;false&#39; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bash

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;setupEnv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Set up environments
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;timeout-minutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Checkout
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v2.3.4
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;persist-credentials&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; setup environment variables
        &lt;span class=&quot;token key atrule&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; setupEnv
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          echo &quot;Setup environment and secrets&quot;
          if [ &quot;${{ github.event.inputs.environment }}&quot; = &quot;staging&quot; ] || [ &quot;${{ github.ref }}&quot; = &quot;refs/heads/dev&quot; ]; then
            echo &quot;::set-output name=environment::staging&quot;
            echo &quot;::set-output name=githubEnvironment::Development&quot;
          elif [ &quot;${{ github.event.inputs.environment }}&quot; = &quot;prod&quot; ] || [ &quot;${{ github.ref }}&quot; = &quot;refs/heads/master&quot; ]; then
            echo &quot;::set-output name=environment::prod&quot;
            echo &quot;::set-output name=githubEnvironment::Production&quot;
          fi&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;outputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; steps.setupEnv.outputs.environment &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;githubEnvironment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; steps.setupEnv.outputs.githubEnvironment &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; jamcomments&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; steps.setupEnv.outputs.environment &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;eus&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;fapp

  &lt;span class=&quot;token key atrule&quot;&gt;build-and-deploy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build app and deploy to Azure
    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; needs.setupEnv.outputs.githubEnvironment &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;concurrency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; needs.setupEnv.outputs.name &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; setupEnv
    &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; needs.setupEnv.outputs.environment &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Checkout GitHub Action&#39;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@master

    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Setup Node $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env.NODE_VERSION &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; Environment
      &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v1
      &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env.NODE_VERSION &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;yarn&#39;&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Resolve Project Dependencies Using yarn&#39;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bash
      &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
        yarn
        yarn build
        yarn test&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Azure Login
      &lt;span class=&quot;token key atrule&quot;&gt;timeout-minutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; azure/login@v1
      &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;creds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.AZURE_CREDENTIALS &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Create infrastructure
      &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;  infra/azure/apps/api/scripts/create&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;funcapp.sh
      &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;DB_PASSWORD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.DB_PASSWORD &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install zip
      &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; montudor/action&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;zip@v1

    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Deploy to Azure Functions&#39;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; infra/azure/apps/api/scripts/deploy&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;funcapp.sh

    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Azure Logout
      &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; always() &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
        az logout
        az cache purge
        az account clear&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;final-thoughts&quot; tabindex=&quot;-1&quot;&gt;Final thoughts &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2022/automated-deployment-linux-azure-functions-app/#final-thoughts&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This guide is a product of many trials and errors I have made attempting to find a working solution for automatically deploying a function app to Azure Functions, with specific issues related to the Consumption Plan on Linux. It&#39;s by no means the best possible solution. It works but should be improved.&lt;/p&gt;
&lt;p&gt;One thing that really bugs me is that the resources are not created in a transactional fashion. Even within a single ARM template, if an error happens while provisioning a nested resource, such as a database configuration item, whatever happened before that is kept in place.&lt;/p&gt;
&lt;p&gt;This means that a resource can exist in an inconsistent state, potentially missing vital configuration or being exposed to exploits.&lt;/p&gt;
&lt;p&gt;This is exacerbated by the fact we need to run multi-step procedural scripts to actually deploy the application code. The definition for code deployment would ideally look just like the ARM templates that lay out the desired state of a given resource - declaratively, not procedurally.&lt;/p&gt;
&lt;p&gt;With the caveats admitted and even emphasized, I still enjoy this way of managing and deploying code and all its dependencies way more than I do logging into a Linux server with SSH and running the usual suite of tools - &lt;code&gt;apt-get&lt;/code&gt;, &lt;code&gt;vim configfile&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;Once you have figured out the pipeline and all its quirks, you save tons of time worrying about infrastructure, which you can invest more productively.&lt;/p&gt;
&lt;p&gt;Hopefully, this has been useful to you, and if you have any questions or suggestions for improving the setup, be sure to leave a comment below!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>How to teach your Node.js app to speak SQL without an ORM</title>
		<link
      href="https://tomaskohl.com/code/2021/how-to-teach-your-nodejs-app-to-speak-sql-without-orm/"
    />
		<updated>2021-08-18T12:00:00Z</updated>
		<id
    >https://tomaskohl.com/code/2021/how-to-teach-your-nodejs-app-to-speak-sql-without-orm/</id>
		<content
      type="html"
    >&lt;p&gt;Confession: I love SQL. I love its declarative nature: you tell the database what data you need and how it should look, not &lt;em&gt;how&lt;/em&gt; it should go around accomplishing that.&lt;/p&gt;
&lt;p&gt;I love SQL so much I want to use it in any programming language I happen to be using, and do so directly. No middleman, no abstraction layer, and no ORM. I could write another article discussing the pros and cons of ORMs but let&#39;s leave that aside today. Suffice to say, I find them to be a needless intermediary.&lt;/p&gt;
&lt;p&gt;I also prefer using types, which is why Typescript is my go-to language when working with Node.js. Granted, it is not a standalone language and compiles back to Javascript, and so its type safety is limited to compile-time. Still, its impact on my developer productivity and happiness is difficult to overstate.&lt;/p&gt;
&lt;p&gt;Lastly, my database of choice for pretty much any web app has been PostgreSQL. Let&#39;s see how one could ties these three preferences together.&lt;/p&gt;
&lt;h2 id=&quot;sql-with-typescript&quot; tabindex=&quot;-1&quot;&gt;SQL with Typescript &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-teach-your-nodejs-app-to-speak-sql-without-orm/#sql-with-typescript&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When it comes to accessing a database, a popular choice in the Node.js is &lt;a href=&quot;https://knexjs.org/&quot;&gt;Knex&lt;/a&gt;. It comes with the usual assortment of features such as a schema builder, a query builder, and migrations. I&#39;ve used it on client projects and I&#39;ve got to admit, it is really easy to use.&lt;/p&gt;
&lt;p&gt;It includes support for raw queries, and I could &lt;em&gt;almost&lt;/em&gt; use normal SQL that way, but looks how it looks in practice:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;knex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;raw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;select * from users where id = ?&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Indeed, I supply an array of parameters to replace the placeholders (&lt;code&gt;?&lt;/code&gt;), and I trust Knex will take care of sanitizing the input to prevent SQL injection. What I don&#39;t have is type safety.&lt;/p&gt;
&lt;h2 id=&quot;introducing-pgtyped&quot; tabindex=&quot;-1&quot;&gt;Introducing pgtyped &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-teach-your-nodejs-app-to-speak-sql-without-orm/#introducing-pgtyped&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I was looking for a Typescript library for an experiment I&#39;m running, and found this wonderful repository: &lt;a href=&quot;https://github.com/adelsz/pgtyped&quot;&gt;pgtyped&lt;/a&gt;. Long story short, it has precisely the feature set I was looking for. Quoting from the README:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PgTyped makes it possible to use raw SQL in TypeScript with guaranteed type-safety.&lt;br /&gt;
No need to map or translate your DB schema to TypeScript, PgTyped automatically generates types and interfaces for your SQL queries by using your running Postgres database as the source of type information.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What it means in practice: I write normal SQL queries, put placeholders inside for parameters, and run &lt;code&gt;pgtyped&lt;/code&gt; on my code-base. It will examine the database, infer the data types, map them to Javascript datatypes, and create typed &amp;quot;proxies&amp;quot; that I work with in my data-access layer.&lt;/p&gt;
&lt;p&gt;Thus I both have and eat my cake!&lt;/p&gt;
&lt;h2 id=&quot;practical-example&quot; tabindex=&quot;-1&quot;&gt;Practical example &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-teach-your-nodejs-app-to-speak-sql-without-orm/#practical-example&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/teekay/jcomments/&quot;&gt;JComments&lt;/a&gt; is a headless commenting platform for static websites. I&#39;ve built it with &lt;a href=&quot;https://nestjs.com/&quot;&gt;NestJS&lt;/a&gt; and Postgres. &lt;code&gt;Pgtyped&lt;/code&gt; came on my radar when I was setting up my stack, and has been there from the very first draft of the app.&lt;/p&gt;
&lt;p&gt;The NestJS way of organizing your code makes a heavy use of modules and I quite enjoy its powerful dependency injection container, even though I burned myself many times when getting familiar with the framework.&lt;/p&gt;
&lt;p&gt;Inside each module, I have services that provide functionality specific to the module&#39;s problem domain, as well as a data access objects. For example, the &lt;code&gt;comments&lt;/code&gt; module that serves out comments for a given URL over an API has these files inside:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;comments.service&lt;/code&gt; that is used by the API controller and mediates the data access&lt;/li&gt;
&lt;li&gt;&lt;code&gt;comments.interface&lt;/code&gt; that contains domain interfaces&lt;/li&gt;
&lt;li&gt;&lt;code&gt;comments.sql&lt;/code&gt; that has raw SQL queries used by the service class&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When running &lt;code&gt;pgtyped&lt;/code&gt; in my project folder, it generates a fourth file: &lt;code&gt;comments.queries&lt;/code&gt;. There&#39;s generated code inside that contains proxies for query parameter objects as well as proxies for the returned results. For instance, given this SQL query:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* @name commentsForAccountPaged */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; comments &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; account_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;:accountId &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; created_at &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LIMIT&lt;/span&gt; :&lt;span class=&quot;token keyword&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OFFSET&lt;/span&gt; :&lt;span class=&quot;token keyword&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The proxy for the query parameters looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ICommentsForAccountPagedParams&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  accountId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  limit&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  offset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the proxy for the resultset looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ICommentsForAccountPagedResult&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  account_id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  page_url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  comment&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  reader_name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  reader_email&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  reader_website&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  created_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  page_title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The object that runs the query have a name determined by the annotation preceding the SQL statement, in this case &lt;code&gt;commentsForAccountPaged&lt;/code&gt;. It has a method &lt;code&gt;run()&lt;/code&gt;, which takes the &lt;code&gt;ICommentsForAccountPagedParams&lt;/code&gt; interface as its parameter and returns an array of &lt;code&gt;ICommentsForAccountPagedResult&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is all tied together in the service method:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;commentsForAccountPaged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;account&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Account&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; batchSize&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; page&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;CommentWithId&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; limit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; batchSize &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; offset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;page &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; limit
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; accountId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; account&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; limit&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;limit&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; offset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;offset&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pagedComments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; commentsForAccountPaged&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;interpretedQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;commentsForAccountPaged&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; pagedComments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;recordToClass&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can&#39;t make a mistake by passing it parameters of an incorrect type, and I know exactly what the object coming out of the database looks like. The last statement of the method merely maps the auto-generated object into my domain object.&lt;/p&gt;
&lt;p&gt;I find this approach refreshing as it lets me speak SQL. It&#39;s an analogy to the &amp;quot;exposed brick&amp;quot; trend in apartment construction. We speak to databases daily, and often hide this fact behind the facades of ORMs and other middlemen. Why? The database is a foundational technology. And anything beyond a simple CRUD app will eventually tie itself to the specific database chosen for the project. We can just as well speak with it using its native tongue.&lt;/p&gt;
&lt;p&gt;There&#39;s an argument to be made that as a project grows, the number of SQL queries written grows as well. Yes, code duplication can be an issue, if you have multiple queries that kinda do the same thing but not exactly. I just don&#39;t perceive this to be a problem: just by looking into a single SQL file, I know exactly how many queries I am using, what they look like, and can make refactoring decisions based on that.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-teach-your-nodejs-app-to-speak-sql-without-orm/#conclusion&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ve been enamored with &lt;code&gt;pgtyped&lt;/code&gt; and recommend that you take a look at it if you are developing Typescript apps with Postgres. I found it to be highly usable for a normal web app with standard database needs.&lt;/p&gt;
&lt;p&gt;It&#39;s a big difference if you&#39;re coming over from the ORM world, and my goal is not to talk you out of using ORMs. If you like working with plain SQL queries and statements instead, then you&#39;ll be quick to appreciate it.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>How to localize text with a binding in WPF</title>
		<link
      href="https://tomaskohl.com/code/2021/wpf-binding-text-with-localizable-placeholder/"
    />
		<updated>2021-01-30T14:45:00Z</updated>
		<id
    >https://tomaskohl.com/code/2021/wpf-binding-text-with-localizable-placeholder/</id>
		<content
      type="html"
    >&lt;p&gt;Recently, I faced a following dilemma: I was binding a &lt;code&gt;TextBlock&lt;/code&gt; to a string property of a class instance, and when that instance was null, I wanted to show a placeholder that had to be localizable. Given this example:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TextBlock&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;x:&lt;/span&gt;Uid&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Track_Title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
           &lt;span class=&quot;token attr-name&quot;&gt;TextWrapping&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;WrapWithOverflow&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; 
           &lt;span class=&quot;token attr-name&quot;&gt;FontWeight&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Bold&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
           &lt;span class=&quot;token attr-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{Binding Meta.Title}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- what if it&#39;s null? --&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What would be the right way to do it?&lt;/p&gt;
&lt;p&gt;One option would be to place another &lt;code&gt;TextBlock&lt;/code&gt; next to it, bind it to the placeholder value, and only show it when the result of the binding expression was empty:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TextBlock&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;FontWeight&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Bold&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
           &lt;span class=&quot;token attr-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{loc:Loc NO_TRACK}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TextBlock.Style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Style&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;TargetType&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;TextBlock&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;
      &amp;lt;Setter Property=&lt;span class=&quot;token string&quot;&gt;&quot;Visibility&quot;&lt;/span&gt; Value=&lt;span class=&quot;token string&quot;&gt;&quot;Collapsed&quot;&lt;/span&gt; /&gt;
      &amp;lt;Style.Triggers&gt;
        &amp;lt;DataTrigger Binding=&lt;span class=&quot;token string&quot;&gt;&quot;{Binding Meta.Artist}&quot;&lt;/span&gt;
                     PresentationTraceSources.TraceLevel=&lt;span class=&quot;token string&quot;&gt;&quot;High&quot;&lt;/span&gt;
                     Value=&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&gt;
          &amp;lt;Setter Property=&lt;span class=&quot;token string&quot;&gt;&quot;Visibility&quot;&lt;/span&gt; Value=&lt;span class=&quot;token string&quot;&gt;&quot;Visible&quot;&lt;/span&gt;/&gt;
        &amp;lt;/DataTrigger&gt;
      &amp;lt;/Style.Triggers&gt;
    &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;TextBlock.Style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;TextBlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are wondering what&#39;s the &lt;code&gt;{loc: Loc}&lt;/code&gt; expression, have a look at my &lt;a href=&quot;https://tomaskohl.com/code/2020-06-24/localizing-wpf-app-on-net-core/&quot;&gt;article about localizing a WPF app on .NET Core&lt;/a&gt; from last year.&lt;/p&gt;
&lt;p&gt;This technique of using separate placeholder elements has the advantage of being explicit at the cost of cluttering the XAML. I wanted something simpler, perhaps using a custom &lt;code&gt;IValueConverter&lt;/code&gt; to provide the placeholder value when the binding source was null:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TextBlock&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;x:&lt;/span&gt;Uid&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Track_Title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
           &lt;span class=&quot;token attr-name&quot;&gt;TextWrapping&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;WrapWithOverflow&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; 
           &lt;span class=&quot;token attr-name&quot;&gt;FontWeight&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Bold&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
           &lt;span class=&quot;token attr-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{Binding Meta.Title, 
              Converter={StaticResource FallbackStringConverterSimple}, 
              ConverterParameter=&#39;NO_TRACK&#39;}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- null averted --&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The converter would take the result of the binding expression, return it if it&#39;s a non-empty string, and when not, look up the fallback value in a dictionary (encapsulated in an abstract class from which it is derived) and provide that instead:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FallbackStringConverterSimple&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;BaseFallbackConverter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IValueConverter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Convert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Type&lt;/span&gt; targetType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CultureInfo&lt;/span&gt; culture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; desired &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;desired&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; desired&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; fallback &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parameter &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fallback&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Binding&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DoNothing&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; replacement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; TranslationSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Instance&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;Dictionary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;fallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; replacement&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ConvertBack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Type&lt;/span&gt; targetType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CultureInfo&lt;/span&gt; culture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NotImplementedException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, and it also has one limitation: were the user to switch languages in the app, e.g. from English to German, the placeholder value would not be updated and would still show the English translation.&lt;/p&gt;
&lt;p&gt;The reason for this behavior is that the values returned from an &lt;code&gt;IValueConverter&lt;/code&gt; are &amp;quot;final&amp;quot;, and since the binding source did not change while the user switched the UI language, there was no impulse to re-evaluate the binding.&lt;/p&gt;
&lt;p&gt;The trick I originally wanted to write about today is to use a custom &lt;code&gt;IMultiValueConverter&lt;/code&gt; and create a sneaky little binding for the placeholder value.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;IMultiValueConverter&lt;/code&gt; interface accepts an array of objects on which it works, and so we can easily pass it the source element itself (the &lt;code&gt;TextBlock&lt;/code&gt;) as well as the result of the original binding. A converter parameter can be used to pass in the fallback.&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FallbackStringConverter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;BaseFallbackConverter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IMultiValueConverter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Convert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; values&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Type&lt;/span&gt; targetType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CultureInfo&lt;/span&gt; culture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;values &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; values&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Length &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; 
            values&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TextBlock&lt;/span&gt; textBlock&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Binding&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DoNothing&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; desired &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; values&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; fallback &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parameter &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsEmptyString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;desired&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;IsEmptyString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fallback&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// return the desired text if not empty or when the fallback is not available&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; desired&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; binding &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Binding&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            Mode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BindingMode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OneWay&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; TranslationSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Instance&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;PropertyPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;[&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;Dictionary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;fallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;]&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        BindingOperations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;SetBinding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;textBlock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TextBlock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TextProperty&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; binding&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Empty&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/** 🤔 **/&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ConvertBack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Type&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt; targetType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CultureInfo&lt;/span&gt; culture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NotImplementedException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The converter will again return the binding result if non-empty. When it&#39;s time to provide the fallback instead, it creates a new binding treating the fallback passed in as the third method parameter as a key in the translation dictionary and applying the binding to the &lt;code&gt;TextBlock&lt;/code&gt;. And, notice the empty return value, which made me scratch my head.&lt;/p&gt;
&lt;p&gt;It works - to the extent that when the result of the binding expression is empty, the converter steps in and provides the fallback, and the fallback is localized in real-time, responding to UI language changes. Something is definitely fishy about this trick, though.&lt;/p&gt;
&lt;p&gt;It took me about a day to realize where I was wrong: once the binding expression was empty, I replaced the original binding with the new one, binding to the fallback value, which meant the fallback would stay there forever! 🤣&lt;/p&gt;
&lt;p&gt;Time to educate myself.&lt;/p&gt;
&lt;p&gt;In the end, I learned about &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.windows.data.prioritybinding?view=net-5.0&quot;&gt;&lt;code&gt;PriorityBinding&lt;/code&gt;&lt;/a&gt; and was able to get rid of the converter altogether. The final version is:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TextBlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TextBlock.Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;PriorityBinding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Binding&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;SelectedTrack.Meta.Title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Binding&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{x:Static loc:TranslationSource.Instance}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
               &lt;span class=&quot;token attr-name&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;[Common.i18n.Strings.NO_TRACK]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;PriorityBinding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;TextBlock.Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;TextBlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;PriorityBinding&lt;/code&gt; will return the value of the first binding expression if not empty, and default to the 2nd otherwise.&lt;/p&gt;
&lt;p&gt;Notice two things: the fallback binding uses a static access to the &lt;code&gt;TranslationSource&lt;/code&gt; singleton instance, and this is one of a few cases when the singleton pattern helps.&lt;/p&gt;
&lt;p&gt;Second, the value of the &lt;code&gt;Path&lt;/code&gt; property refers to the entire path, which comprises of a dictionary for the current assembly and the key to the translatable resource. I think having the path specified like this is a definite weakness of this approach, let alone the fact that that it&#39;s a string and Intellisense will no longer help me if I change the translation key later or move it to another assembly.&lt;/p&gt;
&lt;p&gt;Nevertheless, I think this solution is practical enough to warrant consideration.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>How to migrate from Ghost to Eleventy</title>
		<link
      href="https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/"
    />
		<updated>2021-01-30T14:45:00Z</updated>
		<id>https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/</id>
		<content
      type="html"
    >&lt;p&gt;My &lt;a href=&quot;https://tomaskohl.com/tango/&quot;&gt;tango blog&lt;/a&gt; used to run on the &lt;a href=&quot;https://ghost.org/&quot;&gt;Ghost&lt;/a&gt; publishing platform. It&#39;s fast and comfortable, but since the rest of this website is built statically on &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;, I wanted move away from Ghost. Here is what I learned in the process.&lt;/p&gt;
&lt;h2 id=&quot;reasons-to-switch&quot; tabindex=&quot;-1&quot;&gt;Reasons to switch &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/#reasons-to-switch&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&#39;s have a look at my reasons. Yours and mine are going to be different.&lt;/p&gt;
&lt;h3 id=&quot;background&quot; tabindex=&quot;-1&quot;&gt;Background &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/#background&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;My tango blog is already mostly static. There was backend with the Ghost admin app that I used to compose my articles. Once published, I had no reason to go there.&lt;/p&gt;
&lt;p&gt;Ghost has some integrations. I didn&#39;t use them. It has a membership functionality. I didn&#39;t use it.&lt;/p&gt;
&lt;p&gt;Perhaps surprisingly, Ghost doesn&#39;t, to this day, have built-in comments. I missed them, and integrated the &lt;a href=&quot;https://posativ.org/isso/&quot;&gt;Isso&lt;/a&gt; self-hosted comments, there and here. It&#39;s a little bit of Python code with SQLite. Not that there&#39;s much commenting going on here anyway.&lt;/p&gt;
&lt;h3 id=&quot;that-one-fear-that-made-me-do-it&quot; tabindex=&quot;-1&quot;&gt;That one fear that made me do it &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/#that-one-fear-that-made-me-do-it&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I built a solid collection of articles, and I would be supremely pissed if I lost them.&lt;/p&gt;
&lt;p&gt;I ran Ghost on SQLite. I trust it but hey, it&#39;s a database. I wasn&#39;t going to check it in into the repository, and I set up semi-manual backups; still, I felt uneasy. Something could go wrong, the database might become corrupted.&lt;/p&gt;
&lt;p&gt;All my uploads (mostly images accompanying my posts) are on the filesystem, not in the database.  I&#39;d have to back them up, too, if I wanted to be thorough.&lt;/p&gt;
&lt;p&gt;And then there&#39;s the rest of this website, which lives happily in a Git repository. One that automatically takes care of all my content is backed up in several places. I knew that if I could take my blog&#39;s content and put it in my git repo, my fear would go away instantly.&lt;/p&gt;
&lt;p&gt;Other considerations played little to no role. Ghost is pretty fast, and the blog&#39;s performance didn&#39;t go up after the initial migration. You can self-host it at no cost, so price was also not a concern.&lt;/p&gt;
&lt;h2 id=&quot;how-i-did-it&quot; tabindex=&quot;-1&quot;&gt;How I did it &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/#how-i-did-it&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before diving in, I assumed this was going to be a lengthy process. Not so much. The initial import took about a day and I went live in a week.&lt;/p&gt;
&lt;p&gt;First, Ghost lets you export your content into JSON. Put this JSON in an empty folder and install the package &lt;code&gt;ghost-to-md&lt;/code&gt;. The version I used was &lt;code&gt;1.3.0&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; i ghost-to-md&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then point it to your exported JSON like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;.&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;node_modules&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;.bin&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;ghost-to-md &lt;span class=&quot;token variable&quot;&gt;$PATH_TO_GHOST_JSON_FILE&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script will chew through your JSON and create a folder &lt;code&gt;ghost-to-md-output&lt;/code&gt; with all your glorious articles converted to Markdown.&lt;/p&gt;
&lt;p&gt;I was pretty impressed by this, because I checked the SQLite database and Ghost stores your articles as HTML, and so a back conversion had to take place.&lt;/p&gt;
&lt;p&gt;It succeeded with remarkable precision with only a few misses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;YouTube embeds&lt;/li&gt;
&lt;li&gt;Spotify embeds&lt;/li&gt;
&lt;li&gt;Bandcamp embeds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You&#39;ll notice the pattern here.&lt;/p&gt;
&lt;p&gt;I was going to go through all my articles with a magnifying glass anyway, so it wasn&#39;t such a big deal. Just be aware of it. Anything contained within an &lt;code&gt;&amp;lt;iframe...&amp;gt;&lt;/code&gt; gets quietly ignored.&lt;/p&gt;
&lt;p&gt;How much work is left for you depends on your existing Eleventy setup. I will describe what I had to do.&lt;/p&gt;
&lt;h3 id=&quot;the-basics&quot; tabindex=&quot;-1&quot;&gt;The basics &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/#the-basics&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.11ty.dev/docs/permalinks/&quot;&gt;Cool URLs don&#39;t change, right?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As luck would have it, the &lt;code&gt;ghost-to-md&lt;/code&gt; exported the &lt;code&gt;slug&lt;/code&gt; property of each post&#39;s metadata, which happens to be what Ghost to used as the final part of the path in the URL.&lt;/p&gt;
&lt;p&gt;And so, if the slug were &lt;code&gt;my-awesome-cd-review&lt;/code&gt;, the relative URL would be &lt;code&gt;/tango/my-awesome-cd-review/&lt;/code&gt;. Given that by default, Eleventy maps folder and file names to URL components, all it took me to keep the URLs the same was to place my posts in a folder &lt;code&gt;./$ROOT_CONTENT_DIR/tango&lt;/code&gt; and instruct Eleventy to use the &lt;code&gt;slug&lt;/code&gt; metadata variable to construct the permalink.&lt;/p&gt;
&lt;p&gt;The way to do this is to make each post use the same template, and in the template specify this format of the permalink:&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;permalink: /tango/{{ slug }}/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No additional configuration / programming required.&lt;/p&gt;
&lt;h3 id=&quot;the-fine-tuning&quot; tabindex=&quot;-1&quot;&gt;The fine-tuning &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/#the-fine-tuning&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I copied the script&#39;s output under my &lt;code&gt;_site&lt;/code&gt; folder, where all my content lives. The articles I post under &lt;a href=&quot;https://tomaskohl.com/code/&quot;&gt;/code&lt;/a&gt; use slightly different metadata, but I was going to build new templates for my tango content, and so that wasn&#39;t an immediate problem.&lt;/p&gt;
&lt;p&gt;Adapting my Ghost theme to Eleventy took just a little while. I took all the styles and HTML as-is from the theme. Ghost uses Handlebars while I use mostly Nunjucks on Eleventy, so it took a bit to convert the variables, loops, etc.&lt;/p&gt;
&lt;p&gt;The 90% of the work was going through the articles and locating the missing bits. There might&#39;ve been a way to automate that but I wanted to make sure with my own eyes anyway.&lt;/p&gt;
&lt;p&gt;There were occasional hiccups.&lt;/p&gt;
&lt;h4 id=&quot;rss&quot; tabindex=&quot;-1&quot;&gt;RSS &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/#rss&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;For instance, the RSS that Ghost generates uses the post&#39;s &lt;code&gt;id&lt;/code&gt; from the database for the &lt;code&gt;guid&lt;/code&gt; property on the &lt;code&gt;item&lt;/code&gt; element. That part wasn&#39;t exported to the Markdown, and so I ran a custom script on the IDs like this, saving the output to a file:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./tango-is-alive.ghost.2021-01-23-12-03-06.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;posts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;slug&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slug&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I placed the file in the &lt;code&gt;_site/data&lt;/code&gt; ensuring that it would be available to Eleventy during build. Then the template for my RSS feed has this bit to use the &amp;quot;legacy&amp;quot; Ghost ID for the existing content:&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;guid&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;isPermaLink&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;false&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{% if item.data.ghost_id %}{{ item.data.ghost_id }}{% else %}{{ item.data.slug }}|{{ item.data.date_updated }}{% endif %}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;guid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously my new articles aren&#39;t going to have this ID but I wanted to avoid making the RSS aggregators think ALL of my content is new / fresh / updated, and spamming my subscribers with everything I&#39;ve got so far.&lt;/p&gt;
&lt;h4 id=&quot;responsive-images&quot; tabindex=&quot;-1&quot;&gt;Responsive images &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/#responsive-images&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Ghost generates multiple versions of the images you upload via the Admin app so that the browser can fetch whichever version is most appropriate given the available screen width.&lt;/p&gt;
&lt;p&gt;I had these versions already generated for the existing content. Wouldn&#39;t it be nice to have a feature like this for my future posts, however?&lt;/p&gt;
&lt;p&gt;It turned out Ghost uses the &lt;a href=&quot;https://sharp.pixelplumbing.com/&quot;&gt;sharp&lt;/a&gt; library for this task, and so could I. It took me about an hour to write a script that resizes everything that hasn&#39;t been already resized, and I hooked it up to be executed on each build like this (this is from within &lt;code&gt;eleventy.js&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;afterBuild&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Run me after the build ends&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// I started writing in 2018, and so that&#39;s why we start there :)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; years &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2018&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFullYear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;toString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;makeImagesAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;years&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeImagesAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;years&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; year &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; years&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeResponsiveImages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;year&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Generated responsive images&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;ll notice there&#39;s a missing await at the end of the &lt;code&gt;afterBuild&lt;/code&gt; hook. Last time I checked, there wasn&#39;t a way to hook up an async event handler here.&lt;/p&gt;
&lt;p&gt;Here&#39;s the full script that does the resizing:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;use strict&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;lodash&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sharp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sharp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; responsiveImageSizes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;320&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;640&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;960&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1920&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; imageRoot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;build&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tango&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;content&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;images&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeResponsiveImages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;rootDir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; realRoot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageRoot&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rootDir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dir&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    dir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;promises&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;opendir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;realRoot&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;oops&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Could not open &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;dir&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;oops&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dirent &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dirent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDirectory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeResponsiveImages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rootDir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dirent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dirent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resizeImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rootDir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supported &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jpg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filenameNorm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filename &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;supported&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; filenameNorm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;s&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resizeImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dirs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filename&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sep&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dirs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; dirs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;responsiveImageSizes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wDir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;width&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; respDir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageRoot&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; wDir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;existsSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;respDir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mkdirSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;respDir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;recursive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;existsSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;respDir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sharp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageRoot&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;withoutEnlargement&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;respDir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;makeResponsiveImages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; makeResponsiveImages&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-deployment&quot; tabindex=&quot;-1&quot;&gt;The deployment &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/#the-deployment&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I have come across intermittent build failures, for which I submitted &lt;a href=&quot;https://github.com/11ty/eleventy/issues/1615&quot;&gt;an issue&lt;/a&gt;. Those worried me enough that I introduced a two-step deployment process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, build everything as normal&lt;/li&gt;
&lt;li&gt;Verify that no errors came up during build&lt;/li&gt;
&lt;li&gt;Rename the &lt;code&gt;build&lt;/code&gt; folder to &lt;code&gt;live&lt;/code&gt;, from which content is actually served&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One Powershell script takes care of the first point, then another does the third. It would be nice if I could fully automate this, e.g. by re-generating the website upon each git commit to &lt;code&gt;master&lt;/code&gt;, but then I would have to trust the software again. For my purposes here, no need.&lt;/p&gt;
&lt;p&gt;Upon the first deployment of my now fully static website, I had to change the nginx config to stop redirecting the &lt;code&gt;/tango/&lt;/code&gt; path to the Ghost instance, and finally stop it when I confirmed all was well.&lt;/p&gt;
&lt;h2 id=&quot;closing-thoughts&quot; tabindex=&quot;-1&quot;&gt;Closing thoughts &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2021/how-to-migrate-from-ghost-to-eleventy/#closing-thoughts&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ve been super happy with Eleventy since I first rolled out the current version of my website. Other than their pandering to BLM on their front page, without which I would be even happier, the software is solid and most importantly, my website doesn&#39;t rely on it after it&#39;s built.&lt;/p&gt;
&lt;p&gt;It&#39;s good to know that everything is backed up in git, that there&#39;s no database to corrupt or lose, and should I get hit with a wave of visitors, the performance is only limited by nginx.&lt;/p&gt;
&lt;p&gt;Do I miss Ghost&#39;s visual editor? Yes, it&#39;s fantastic, and so is VS Code. Whoever is managing his or her website using Eleventy is likely technical enough to handle writing Markdown (or HTML) by hand.&lt;/p&gt;
&lt;p&gt;Go static, young man. Go static!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Localizing a WPF app running on .NET Core 3 in 2020</title>
		<link
      href="https://tomaskohl.com/code/2020-06-24/localizing-wpf-app-on-net-core/"
    />
		<updated>2020-06-24T12:00:00Z</updated>
		<id>https://tomaskohl.com/code/2020-06-24/localizing-wpf-app-on-net-core/</id>
		<content
      type="html"
    >&lt;p&gt;I have recently updated my hobby desktop app, &lt;a href=&quot;https://tomaskohl.com/apps/bewitched/&quot;&gt;Bewitched&lt;/a&gt;, from .NET 4.6 to .NET Core 3.1. The process was mostly painless as I was late to the game and most Nugets were already updated for .NET Core 3 / Netstandard support. I lost no functionality - except the ability to localize the UI.&lt;/p&gt;
&lt;p&gt;The app runs in English, Czech, German, and Spanish, and the localization was powered by &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/ms746621.aspx&quot;&gt;LocBaml&lt;/a&gt;, a legacy tool from Microsoft that was never fully supported and the development of which ended sometime 2005 or so.&lt;/p&gt;
&lt;h3 id=&quot;good-bye%2C-locbaml&quot; tabindex=&quot;-1&quot;&gt;Good-bye, LocBaml &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-06-24/localizing-wpf-app-on-net-core/#good-bye%2C-locbaml&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ancient as it is, LocBaml had one distinct advantage over competing approaches: I could leave literal strings in the XAML files, mark the elements containing them with &lt;code&gt;Uid&lt;/code&gt;s, and the tool would extract them for me to translate. During build, the translations are then put into satellite assemblies. When the app starts, it decides which locale to use (based on user preferences) and loads the corresponding assembly.&lt;/p&gt;
&lt;p&gt;The advantage of strings literals in XAML is hard to underrate. XAML looks and feels like HTML (for us web devs), and having human-readable strings makes it so much easier to edit, in the text mode as well as in the XAML Designer.&lt;/p&gt;
&lt;p&gt;You see, the alternative route is to use RESX files, put your dictionaries there (originals as well as translations) and use keys instead of string literals in your XAML.&lt;/p&gt;
&lt;p&gt;Compare this:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;x:&lt;/span&gt;Uid&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ClickMeButton&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Click me!&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to this:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{ns:Loc ClickMeButtonText}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One disadvantage LocBaml has is, you cannot (easily) switch locales at runtime. Speculatively speaking, you might be able to re-load all of the UI but I haven&#39;t tried and the effort to make it worth would likely be disproportionate to the value delivered.&lt;/p&gt;
&lt;p&gt;Nevertheless, LocBaml does not work with .NET Core 3 assemblies. What&#39;s a man to do? Go back to even more historical RESX files, of course!&lt;/p&gt;
&lt;h3 id=&quot;learning-to-love-resx-again&quot; tabindex=&quot;-1&quot;&gt;Learning to love RESX again &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-06-24/localizing-wpf-app-on-net-core/#learning-to-love-resx-again&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;My first steps were to re-learn what localization approaches exist for WPF. To my dismay, there hasn&#39;t been any ground-breaking new technique developed since I first check. I wanted to keep my string literals but that wasn&#39;t to be.&lt;/p&gt;
&lt;p&gt;Chance led me to &lt;a href=&quot;https://www.codinginfinity.me/posts/localization-of-a-wpf-app-the-simple-approach/&quot;&gt;this article&lt;/a&gt; about a simple approach of localizing a WPF app. It uses RESX files but with Bindings, and that means I could potentially switch locales at runtime, could I?&lt;/p&gt;
&lt;p&gt;Yes, yes, that&#39;s indeed what this means.&lt;/p&gt;
&lt;p&gt;A link at the bottom of the article then led me to &lt;a href=&quot;https://github.com/Jinjinov/wpf-localization-multiple-resource-resx-one-language&quot;&gt;this Github repo&lt;/a&gt;, that adds support for multiple dictionaries.&lt;/p&gt;
&lt;p&gt;Bewitched has multiple assemblies and even multiple frontends, and so this was directly applicable to my problem. I would not have to centralize all the texts but put them where they belong.&lt;/p&gt;
&lt;p&gt;This solution is comparable to the &lt;a href=&quot;https://github.com/XAMLMarkupExtensions/WPFLocalizationExtension&quot;&gt;WPF Localization Extension&lt;/a&gt;, which seems to be very popular. It&#39;s far lighter than that, however, just one C# file. I decided I could live without the extra complexity and features. The lighter my dependencies the better I think.&lt;/p&gt;
&lt;h3 id=&quot;my-learning-steps&quot; tabindex=&quot;-1&quot;&gt;My learning steps &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-06-24/localizing-wpf-app-on-net-core/#my-learning-steps&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I have not used RESX files before, and so there was some learning curve to climb.&lt;/p&gt;
&lt;p&gt;I created a file Strings.resx and one for each translation, i.e., Strings.cs.resx, Strings.de.resx, etc. For the dictionaries to be usable outside their assembly, I had to set the &amp;quot;Custom Tool&amp;quot; property to &lt;code&gt;PublicResXFileCodeGenerator&lt;/code&gt;, otherwise they would only have the &lt;code&gt;internal&lt;/code&gt; visibility.&lt;/p&gt;
&lt;p&gt;To have a nice tree-view of related RESX files, I used the &lt;code&gt;DependentUpon&lt;/code&gt; property of the &lt;code&gt;EmbeddedResource&lt;/code&gt; declaration in the project file like this:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ItemGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;EmbeddedResource&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Update&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;i18n\Strings.resx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Generator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;PublicResXFileCodeGenerator&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Generator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;LastGenOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Strings.Designer.cs&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;LastGenOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;EmbeddedResource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;EmbeddedResource&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Update&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;i18n\Strings.en-US.resx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Generator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;PublicResXFileCodeGenerator&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Generator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;DependentUpon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Strings.resx&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;DependentUpon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;EmbeddedResource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;EmbeddedResource&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Update&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;i18n\Strings.cs.resx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Generator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;PublicResXFileCodeGenerator&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Generator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;DependentUpon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Strings.resx&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;DependentUpon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;EmbeddedResource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;EmbeddedResource&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Update&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;i18n\Strings.de.resx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Generator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;PublicResXFileCodeGenerator&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Generator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;DependentUpon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Strings.resx&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;DependentUpon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;EmbeddedResource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;EmbeddedResource&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Update&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;i18n\Strings.es.resx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Generator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;PublicResXFileCodeGenerator&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Generator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;DependentUpon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Strings.resx&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;DependentUpon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;EmbeddedResource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ItemGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visual Studio support is there but won&#39;t overwhelm you with features. If you&#39;d like to see all translations side-by-side, there&#39;s an extension &lt;a href=&quot;http://visualstudiogallery.msdn.microsoft.com/3b64e04c-e8de-4b97-8358-06c73a97cc68&quot;&gt;ResXManager&lt;/a&gt; for this. I have a Jetbrains subscription, and ended up using its &lt;a href=&quot;https://blog.jetbrains.com/dotnet/2019/12/09/localization-becomes-easier-localization-manager-resharper-2019-3/&quot;&gt;Localization Manager&lt;/a&gt; that does a comparable job.&lt;/p&gt;
&lt;p&gt;Importantly, it can import / export dictionaries as CSV, which turned out to be super helpful.&lt;/p&gt;
&lt;p&gt;One of the artifacts of the LocBaml build process is a bunch of CSV files with strings and in which usercontrol to find them. I wrote a Node.js script that extracted them from these CSVs and merged them into the format that Localization Manager could understood. It looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Path,Name,&quot;en-US&quot;,&quot;cs&quot;,&quot;de&quot;,&quot;es&quot;
Embrace.NetCore3/i18n/Strings,registerview.baml_YourNameLabel,Name,&quot;Jméno:&quot;,&quot;Name:&quot;,&quot;Nombre:&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/teekay/9989a5a9d6c9c2a3131e55a75ab5ac49&quot;&gt;Here&#39;s the gist&lt;/a&gt; of my translation helper.&lt;/p&gt;
&lt;p&gt;I imported the generated CSV into Localization Manager, and only had to add the English dictionary by sifting through my XAML files, extracting the strings and replacing them with their dictionary keys.&lt;/p&gt;
&lt;p&gt;I encountered a few gotchas along the way.&lt;/p&gt;
&lt;p&gt;First and foremost, I thought that the default dictionary (&lt;code&gt;Strings.resx&lt;/code&gt;) would be used if the app did not find one for the selected locale. It turned out I was wrong. The English originals had to be put in the &lt;code&gt;*.en-US.resx&lt;/code&gt; resource, otherwise an exception was thrown, complaining about a missing satellite assembly.&lt;/p&gt;
&lt;p&gt;Second, the XAML designer support is not 100% reliable. Some texts only ever show up as keys, others are translated in design-time, too. I am not a big enough Visual Studio internals geek to understand what&#39;s happening there.&lt;/p&gt;
&lt;h3 id=&quot;usage-in-xaml&quot; tabindex=&quot;-1&quot;&gt;Usage in XAML &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-06-24/localizing-wpf-app-on-net-core/#usage-in-xaml&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This toolkit is intended for use in XAML markup, and it could not be easier to use.&lt;/p&gt;
&lt;p&gt;The setup is straightforward. At the topmost level, you import the namespace and specify the default dictionary to use like this:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;  xmlns:resx=&quot;clr-namespace:Embrace.Common.i18n&quot;
  xmlns:loc=&quot;clr-namespace:WpfLocalizationWithMultipleResourceManagers&quot;
  loc:Translation.ResourceManager=&quot;{x:Static resx:Strings.ResourceManager}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first import points to the namespace where you have your resource files. The second imports the namespace of the localization helper. The third specifies the default dictionary.&lt;/p&gt;
&lt;p&gt;The texts are then translated on-the-go like this:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TextBlock&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{loc:Loc trackeditor_baml_TrackTitleLabel}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
           &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;x:&lt;/span&gt;Uid&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;TrackTitleLabel&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the resource lookup is based on a Binding, the user can  select a different locale in Settings, the app informs the localization helper about the new locale, and all texts are refreshed automatically as a result of PropertyChanged event invokation. Sweet!&lt;/p&gt;
&lt;h3 id=&quot;usage-in-code&quot; tabindex=&quot;-1&quot;&gt;Usage in code &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-06-24/localizing-wpf-app-on-net-core/#usage-in-code&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I have a few scenarios where I need to show a message to the user from code, e.g. a notification. In that case, I don&#39;t have any XAML.&lt;/p&gt;
&lt;p&gt;Using the localization helper from code is not the primary use-case but it&#39;s possible.&lt;/p&gt;
&lt;p&gt;In my App.xaml.cs, I add the default dictionary such that it&#39;s available downstream, and set the culture like this:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;  TranslationSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Instance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AddResourceManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Strings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ResourceManager&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Strings.ResourceManager points to the resources&lt;/span&gt;
  TranslationSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Instance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CurrentCulture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; localCulture&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// localCulture is an instance of CultureInfo here&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The localization helper uses the Singleton pattern (&lt;code&gt;TranslationSource.Instance&lt;/code&gt;), of which I am not a huge fan but it feels appropriate here.&lt;/p&gt;
&lt;p&gt;Later, when I need to access localized strings, I access the singleton and ask for them like this:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; translator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; TranslationSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Instance&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
_statusBarService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StatusBarMessage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; translator&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Embrace.Common.i18n.Strings.FAILED&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;ll notice the hard-coded path to the resource key, which happens to be the fully-qualified name of the resource file (incl. the assembly name). &lt;s&gt;I hope to fix that at some point but &lt;code&gt;nameof(...)&lt;/code&gt; only returns the name without the assembly qualification.&lt;/s&gt; I&#39;ll fix that with &lt;code&gt;typeof(Embrace.Common.i18n.Strings).FullName&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;future-steps&quot; tabindex=&quot;-1&quot;&gt;Future steps &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-06-24/localizing-wpf-app-on-net-core/#future-steps&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I have not given up on my string literals yet. In the future, I hope to come up with an approach that would let me use them again, perhaps adding a build step extracting them into RESX and replacing them with resource keys before compilation. I&#39;ll then update my notes with the solution.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>The truth about puppies</title>
		<link href="https://tomaskohl.com/code/2020-05-21/the-truth-about-puppies/" />
		<updated>2020-05-21T19:00:00Z</updated>
		<id>https://tomaskohl.com/code/2020-05-21/the-truth-about-puppies/</id>
		<content
      type="html"
    >&lt;p&gt;I have wanted to have a dog since I was a kid. Now, at the ripe old age of 43, I&#39;ve finally got one. An amstaff puppy.&lt;/p&gt;
&lt;p&gt;Meet Becky.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tomaskohl.com/code/2020-05-21/Becky.jpg&quot; alt=&quot;Meet Becky.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It&#39;s only been two weeks but I already feel like an expert on all things puppies. And let me tell you: you&#39;ve been lied to. Here, I want to set the record straight. To tell the truth about puppies.&lt;/p&gt;
&lt;p&gt;First things first. Puppies are dumb. Almost as dumb as human babies. I should know, I made three of those. They get smart eventually. With dogs, I am not so sure. I watch the grown up dogs in the dog park and they behave just as dumb as my puppy. So there.&lt;/p&gt;
&lt;p&gt;Doing dumb things all the time, you&#39;ll spend an inordinate amount of time preventing them from happening or dealing with the aftermath. Are you running a business, or a side hustle, or have anything substantial going on in your life? If so, be prepared to put that aside once you get a puppy. You won&#39;t have the time anymore.&lt;/p&gt;
&lt;p&gt;Whoever&#39;s every claimed dogs are smart is either lying or has sank down to their level. You&#39;ll soon learn why. Most of it has to do with their bad eating habits and running into dangerous or gross things.&lt;/p&gt;
&lt;p&gt;As does Becky.&lt;/p&gt;
&lt;p&gt;When getting to know you, she might lick you. You could be forgiven for thinking it&#39;s her way of kissing - far from it. Instead, she is tasting you and calculating the risk profile of biting the flesh off your hand. You know that&#39;s what she does because eventually she&#39;ll start biting  you. And those puppy bites, they hurt! You&#39;ve got to slap the bitch around until she learns not to do that.&lt;/p&gt;
&lt;p&gt;Don&#39;t let her cute facial expression mislead you. She is not a primate like us. When she looks at you, it&#39;s only to get something to eat. And if that&#39;s not forthcoming, she&#39;ll drop the cute face fast, and attack.&lt;/p&gt;
&lt;p&gt;Speaking of eating: yes, dogs are technically carnivores, in that only meat and bones do anything worthwhile for their metabolism. That does not preventing them from munching on anything they stick their faces into, however. And more gross it is, the better!&lt;/p&gt;
&lt;p&gt;Licking dove shit as soon as we get out of the apartment is just the entrée. Cigarette butts, stones, plain dirt, leaves, sticks, shit of other dogs, human shit, she loves this stuff. Until something get stuck in her dirty little mouth, that is. Then you&#39;ve got to pry it open and extract the shit while getting bit.&lt;/p&gt;
&lt;p&gt;Out the door you have to get otherwise she makes a bathroom out of your living room. And so you depart, five, six, seven times a day to walk your dog.&lt;/p&gt;
&lt;p&gt;Whoever invented the phrase &amp;quot;walking a dog&amp;quot; should&#39;ve also invented a more apt phrase for taking a puppy out. Because walk you will not.&lt;/p&gt;
&lt;p&gt;More appropriately, it could be called &amp;quot;sitting with your dog outside&amp;quot; as you can&#39;t take more than two, three steps before she&#39;ll sit down to observe the goings on.&lt;/p&gt;
&lt;p&gt;Offering a treat can help but only to make her move towards you to take it, then she&#39;ll sit down again. Soon enough you start yelling. Lately, I&#39;ve also deployed corporal punishment into my vocabulary but these beasts can take a lot more abuse than I can give.&lt;/p&gt;
&lt;p&gt;If the surroundings are familiar to her, she&#39;ll occasionally run around or even walk by your left leg like a good dog she&#39;s supposed to be. In new areas, she would mostly sit and watch or sniff around like a Mossad spy in a hostile territory.&lt;/p&gt;
&lt;p&gt;Being a puppy, she&#39;s interested in everything. I suppose that&#39;s a good thing. I remember being interested in things.&lt;/p&gt;
&lt;p&gt;Like other people, for instance. They make an inviting gesture, and she runs toward them, ready to cuddle, wagging her tail like it&#39;s the first time she met someone nice. You stand there, leash in your hand, a pathetic cuckold.&lt;/p&gt;
&lt;p&gt;I don&#39;t know about other dog owners but I just hate it when random strangers pet my dog without permission. Something about the act just sets my blood to boiling temperature instantly.&lt;/p&gt;
&lt;p&gt;Other dogs are fine, usually. They get she&#39;s just a puppy, let her sniff their asses, and move on.&lt;/p&gt;
&lt;p&gt;The outing can take anywhere from thirty minutes to three hours. You have to wait until the poop comes out otherwise it comes out in your living room. To be honest, this type of incident has only happened a few times with her so far. That&#39;s because we are out most of the time trying to prevent that from happening again.&lt;/p&gt;
&lt;p&gt;How the puppy behaves in your apartment really depends on your ability to tire her outside. The ideal puppy comes home and sleeps instantly. Before I learned that, I had to watch her at all times to take inappropriate objects of affection out of her mouth.&lt;/p&gt;
&lt;p&gt;I suppose it comes down to expectations vs. reality. And whoever sets these expectations should have a class action coming to wipe them out. It&#39;s all cuddling and affection and unconditional love when it&#39;s someone else&#39;s daily problem, I suppose. In actual reality, on planet Earth, you&#39;ve got to work your ass off to get an ounce of payback out of your puppy.&lt;/p&gt;
&lt;p&gt;Feeding her, playing with her, walking (kinda) her outside, buying things for her amusement... the list is endless. Nothing unconditional about her behavior, quite the opposite. She gets what she wants, maybe you get a lick or a tail-wag. Then back to the grind. When she sleeps, you try to recover. Perhaps you also get some sleep. And then she wags her tail when she sees you after waking up in her cage, tilts her head just a little bit, and a new day begins.&lt;/p&gt;
&lt;p&gt;This is the truth: you are not getting a dog. The dog is getting you.&lt;/p&gt;
&lt;p&gt;PS Yeah, of course I am kidding. Sort-of. I love my Becky, the litte bitch. It gets a little overwhelming at times.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Adventures with Makeblock mBot and Typescript</title>
		<link
      href="https://tomaskohl.com/code/2020-04-25/adventures-with-makeblock-mbot-and-typescript/"
    />
		<updated>2020-04-07T08:00:00Z</updated>
		<id
    >https://tomaskohl.com/code/2020-04-25/adventures-with-makeblock-mbot-and-typescript/</id>
		<content
      type="html"
    >&lt;p&gt;I have so far not been successfully in this &amp;quot;teach your kids programming&amp;quot; thing. My three sons are technical but have not yet demonstrated any inclination to code.&lt;/p&gt;
&lt;p&gt;Thinking I could help them find the inner fire, so to speak, I bought a programmable &amp;quot;robot&amp;quot; from China a few years ago - the &lt;a href=&quot;http://learn.makeblock.com/en/mbot/&quot;&gt;mBot by Makeblock&lt;/a&gt;. It then proceeded to sit on my shelve and collected dust.&lt;/p&gt;
&lt;p&gt;The thing was, I couldn&#39;t get past the block-based programming environment in mBlock IDE. It felt unnatural and constrained - I&#39;ve only ever programmed in a text editor / IDE, and these visual tools confuse me.&lt;/p&gt;
&lt;p&gt;However, having had zero experience with Arduino, upon which the mBot is based, I could just effortlessly drop into the text editor and start hacking. And I suspect that even if I could, it would not be the most friction-less way of teaching my sons to code.&lt;/p&gt;
&lt;p&gt;On the shelf the robot sat.&lt;/p&gt;
&lt;p&gt;Come the Great Corona Lockdown of 2020, I was gifted an extra portion of free time. One day I noticed the blue robot staring at me from the bookshelf, motionless. Hey, buddy! Could we rekindle our friendship?&lt;/p&gt;
&lt;p&gt;It turns out we could. As it happens, I can talk to my robot in Javascript. Or better yet, Typescript.&lt;/p&gt;
&lt;h3 id=&quot;what-can-the-mbot-do%3F&quot; tabindex=&quot;-1&quot;&gt;What can the mBot do? &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-25/adventures-with-makeblock-mbot-and-typescript/#what-can-the-mbot-do%3F&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Simply put: it moves (two wheels with motors at each side with a single front wheel for support), it sings (or rather, squeaks), it blinks (two programmable LEDs), it can sense distance, follow a line, and when equipped with additional sensors, it gains additional powers.&lt;/p&gt;
&lt;p&gt;Importantly, it has Bluetooth, and can wander about as instructed, wirelessly.&lt;/p&gt;
&lt;p&gt;I think that&#39;s a pretty good basis for exploration and tinkering.&lt;/p&gt;
&lt;h3 id=&quot;the-mbot-javascript-sdk&quot; tabindex=&quot;-1&quot;&gt;The mBot Javascript SDK &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-25/adventures-with-makeblock-mbot-and-typescript/#the-mbot-javascript-sdk&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Suffice to say, I am late to the party. Perhaps too late?&lt;/p&gt;
&lt;p&gt;There&#39;s a &lt;a href=&quot;https://github.com/Makeblock-official/mbot_nodebots&quot;&gt;repo by Makeblock&lt;/a&gt; with Javascript SDK that was last updated in December 2016. Getting to run the simplest example on my robot was not trivial.&lt;/p&gt;
&lt;p&gt;I had to downgrade Node to 8.9 to be able to compile the native bindings of some 3rd-party dependencies, which led to me install &lt;a href=&quot;https://github.com/coreybutler/nvm-windows&quot;&gt;nvm for Windows&lt;/a&gt; as I don&#39;t intend to use this ancient version for my daily business.&lt;/p&gt;
&lt;p&gt;The examples then ran but looking at the code, I did not feel motivated to expand on it and work out my own examples. After having adopted Typescript a few years back, I did not want to return to plain-old untyped Javascript.&lt;/p&gt;
&lt;p&gt;What would it take to convert this into a &lt;a href=&quot;https://github.com/teekay/mbot_nodebots/tree/develop&quot;&gt;Typescript project&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;I didn&#39;t want to rework the existing samples, of course, but rather write my own and learn how to talk to my robot in a typed fashion.&lt;/p&gt;
&lt;h3 id=&quot;bringing-the-dependencies-into-2020&quot; tabindex=&quot;-1&quot;&gt;Bringing the dependencies into 2020 &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-25/adventures-with-makeblock-mbot-and-typescript/#bringing-the-dependencies-into-2020&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The first step was to update the dependencies. The repo relies on the &lt;code&gt;johnny-five&lt;/code&gt; and &lt;code&gt;node-pixel&lt;/code&gt; Javascript frameworks to do the heavy lifting, and I wanted to have their latest features at my disposal.&lt;/p&gt;
&lt;p&gt;I bumped Node to 10.*, which seemed to suffice.&lt;/p&gt;
&lt;p&gt;After managing to install everything, which on Windows is not without pitfalls, the code ran without erroring out before reaching my robot but then failed because of an incorrect, or rather unexpected, firmware version.&lt;/p&gt;
&lt;p&gt;The firmware we&#39;re talking about is called &amp;quot;Firmata&amp;quot; and implements a &lt;a href=&quot;https://github.com/firmata/protocol&quot;&gt;protocol&lt;/a&gt; for talking to microcontrollers (such as Arduino).&lt;/p&gt;
&lt;p&gt;What I had to do was add the &lt;code&gt;mBotFirmata&lt;/code&gt; sketch to the latest version (2.5.8) of the &lt;a href=&quot;https://github.com/firmata/arduino&quot;&gt;Arduino firmata&lt;/a&gt;, adding the drivers for the WS2812 LED strips used on the mBot from the &lt;code&gt;node-pixel&lt;/code&gt; &lt;a href=&quot;https://github.com/ajfisher/node-pixel&quot;&gt;repository&lt;/a&gt;, and - surprisingly for me - it almost worked on the first try. Due to a defect in the current version of &lt;code&gt;node-pixel&lt;/code&gt; that apparently does not support the newest firmata, I had to patch a single line in the node-pixel library to get past the startup errors.&lt;/p&gt;
&lt;p&gt;Lo and behold, the LEDs blinked, the the wheels moved, my robot came alive.&lt;/p&gt;
&lt;h3 id=&quot;typescript-ftw&quot; tabindex=&quot;-1&quot;&gt;Typescript FTW &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-25/adventures-with-makeblock-mbot-and-typescript/#typescript-ftw&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Starting from scratch, using Typescript is trivial. Adjust the &lt;code&gt;tsconfig.json&lt;/code&gt; to your liking / standards, and you are good to go.&lt;/p&gt;
&lt;p&gt;It gets trickier with your dependencies and whether they&#39;ve published their types or not. It turned out that &lt;code&gt;johnny-five&lt;/code&gt; has while &lt;code&gt;node-pixel&lt;/code&gt; has not.&lt;/p&gt;
&lt;p&gt;To make my IDE experience smoother, I&#39;ve stubbed some interfaces for objects and their methods from &lt;code&gt;node-pixel&lt;/code&gt; that I&#39;m going to be using and will add to them as I progress. Perhaps I can submit a PR at the end of this exercise.&lt;/p&gt;
&lt;p&gt;It&#39;s hard to overstate how much value Typescript adds to a Javascript project. Even if this is just a toy project, I still want the solid Typescript refactoring experience in VS Code and be able to spot stupid errors and typos in an instant.&lt;/p&gt;
&lt;h3 id=&quot;my-first-mbot-program&quot; tabindex=&quot;-1&quot;&gt;My first mBot program &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-25/adventures-with-makeblock-mbot-and-typescript/#my-first-mbot-program&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let&#39;s not shoot for the Moon and start simple. I want the robot to wander around my sizable working desk, blink, and if it sees an object approaching, emit a panic signal and back-off.&lt;/p&gt;
&lt;p&gt;Current status: it only drives forward and would fall off the desk if I don&#39;t stop it. When it senses my hand, it blinks rapidly and backs off for a moment.&lt;/p&gt;
&lt;p&gt;I don&#39;t think there&#39;s a sensor that would tell the robot it&#39;s about to fall off, and so I might try to put it in a playpen or something.&lt;/p&gt;
&lt;p&gt;You&#39;ll find the code in the &lt;a href=&quot;https://github.com/teekay/mbot_nodebots/blob/develop/toys/scared.ts&quot;&gt;&lt;code&gt;toys/scared.ts&lt;/code&gt;&lt;/a&gt; file if you want to see what I&#39;ve done so far. Not much to write home about yet, frankly.&lt;/p&gt;
&lt;p&gt;It&#39;s a bunch of functions but I&#39;ll convert it to a class as I think the object-paradigm fits it like a glove, given that the mBot &lt;em&gt;is&lt;/em&gt; an actual object in the actual universe.&lt;/p&gt;
&lt;h3 id=&quot;could-i-use-this-to-teach-my-kids-to-code%3F&quot; tabindex=&quot;-1&quot;&gt;Could I use this to teach my kids to code? &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-25/adventures-with-makeblock-mbot-and-typescript/#could-i-use-this-to-teach-my-kids-to-code%3F&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Yes, I think I could, now.&lt;/p&gt;
&lt;p&gt;What I&#39;d like to do is to put together a few useful classes, abstractions on a sufficiently high level that they would feel like those &amp;quot;blocks&amp;quot; from the MBlock IDE without having to be visual.&lt;/p&gt;
&lt;p&gt;I think that since programming is text-based, teaching programming should also be driven by textual interaction. The right level of abstraction is crucial, though, as I&#39;d rather not start from bytes.&lt;/p&gt;
&lt;p&gt;Having said that, the awareness of the existence of bytes (and strings, and numbers, etc.) early on will be useful in the education of my kids, should they eventually become interested. That&#39;s why I&#39;ll continue playing with my mBot with Typescript and not just Javascript. Hopefully I&#39;ll have something interesting to show them someday soon.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>LiteDB vs SQLite: what works better for .NET developers?</title>
		<link href="https://tomaskohl.com/code/2020-04-07/trying-out-litedb/" />
		<updated>2020-04-07T08:00:00Z</updated>
		<id>https://tomaskohl.com/code/2020-04-07/trying-out-litedb/</id>
		<content
      type="html"
    >&lt;p&gt;&lt;a href=&quot;https://sqlite.org/whentouse&quot;&gt;SQLite&lt;/a&gt; has long been the gold standard for desktop, local, or embedded databases. It&#39;s been around forever, is &lt;a href=&quot;https://sqlite.org/testing.html&quot;&gt;unit-tested from top to bottom&lt;/a&gt;, and performs exceptionally well when &lt;a href=&quot;https://sqlite.org/whentouse.html&quot;&gt;used as intended&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;C# developers have used various abstraction layers to access its APIs more in a way that would be more idiomatic. When I looked for one I could use in my desktop app for tango DJs, &lt;a href=&quot;https://tomaskohl.com/apps/bewitched/&quot;&gt;Bewitched&lt;/a&gt;, I went with &lt;a href=&quot;https://github.com/praeclarum/sqlite-net&quot;&gt;SQLite-net&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I use it to this day. Anything &lt;a href=&quot;https://praeclarum.org/&quot;&gt;Frank Krueger&lt;/a&gt; creates is beyond awesome! :)&lt;/p&gt;
&lt;p&gt;Because SQLite is written in C, it isn&#39;t a native citizen of the .NET ecosystem. That is perfectly fine in practice, as the native library will be included in your build, and your domain code doesn&#39;t need to know it&#39;s talking to a non-dotnet code.&lt;/p&gt;
&lt;p&gt;Still, it bugged me occasionally. Could there be a comparable database built &lt;em&gt;for&lt;/em&gt; .NET?&lt;/p&gt;
&lt;p&gt;Well, there is. It&#39;s called &lt;a href=&quot;https://www.litedb.org/&quot;&gt;LiteDB&lt;/a&gt;, and it&#39;s pretty amazing.&lt;/p&gt;
&lt;h3 id=&quot;the-fundamentals-of-litedb&quot; tabindex=&quot;-1&quot;&gt;The fundamentals of LiteDB &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-07/trying-out-litedb/#the-fundamentals-of-litedb&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;LiteDB is 100% C# for .NET 4.5 / NETStandard 1.3/2.0. You run &lt;code&gt;Install-Package LiteDB&lt;/code&gt;, and you get a single DLL. No interop assemblies, no x32/x64 hassles. The advantage of this is hard to overstate.&lt;/p&gt;
&lt;p&gt;Just like SQLite, LiteDB:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;is serverless,&lt;/li&gt;
&lt;li&gt;has ACID transactions,&lt;/li&gt;
&lt;li&gt;uses a single file for your data and indexes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, it supports an external file storage mechanism for blobs greater than 1MB.&lt;/p&gt;
&lt;p&gt;Unlike SQLite, LiteDB is a document database.&lt;/p&gt;
&lt;p&gt;SQLite has tables, which have rows and columns. You define the schema, decide on the data types and choose which fields are required and which can have NULLs.&lt;/p&gt;
&lt;p&gt;LiteDB has collections, just like MongoDB. Collections of documents. Each document has to have an ID, but otherwise it&#39;s up to you to define what the document should contain.&lt;/p&gt;
&lt;p&gt;Its mindset is closer to MongoDB, and if you&#39;ve done any work on the MEAN stack, it will feel very familiar to you.&lt;/p&gt;
&lt;p&gt;Its API is document-oriented. &lt;strong&gt;BUT&lt;/strong&gt; - if you prefer SQL, you can talk to it using a very &lt;a href=&quot;https://www.litedb.org/api/&quot;&gt;SQL-like syntax&lt;/a&gt; - with SELECTs, INSERTs, UPDATEs, and DELETEs  - as of version &lt;strong&gt;5&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The SQL capabilities of LiteDB are impressive. The &lt;a href=&quot;https://www.litedb.org/api/query/&quot;&gt;&lt;code&gt;SELECT&lt;/code&gt; keyword&lt;/a&gt; has &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;GROUP BY&lt;/code&gt;, &lt;code&gt;HAVING&lt;/code&gt; clauses, and you can order your results as well as limit how many are to be returned.&lt;/p&gt;
&lt;h3 id=&quot;first-steps-with-litedb&quot; tabindex=&quot;-1&quot;&gt;First steps with LiteDB &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-07/trying-out-litedb/#first-steps-with-litedb&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can start hacking in Visual Studio or VS Code by referencing the &lt;a href=&quot;https://www.nuget.org/packages/LiteDB&quot;&gt;Nuget package&lt;/a&gt;: &lt;code&gt;Install-Package LiteDB&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Don&#39;t have any code yet? No problem! Download the &lt;a href=&quot;https://github.com/mbdavid/LiteDB.Studio&quot;&gt;LiteDB Studio&lt;/a&gt; instead, and play with the database engine directly.&lt;/p&gt;
&lt;p&gt;What you need to get started is just some JSON you have lying around.&lt;/p&gt;
&lt;p&gt;I exported a sample collection from my local MongoDB instance, cleaned up the JSON to remove &lt;code&gt;ObjectId(...)&lt;/code&gt; and &lt;code&gt;ISODate(...)&lt;/code&gt; references, and imported it into the app. Obviously, you can construct your JSON any way you like.&lt;/p&gt;
&lt;p&gt;When you enter the input file and choose the collection name, the program outputs the import statement for your reference.&lt;/p&gt;
&lt;p&gt;As you will see below, it reads very much like regular SQL:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; $
&lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; addresses
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; $&lt;span class=&quot;token keyword&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;C:/Users/Tomas/Downloads/addresses.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I tried a query on my sample data set:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; $ &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; addresses &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; city &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Venice&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sure enough, the output was what I expected with a data grid of relevant records.&lt;/p&gt;
&lt;p&gt;As this is a document database, you won&#39;t get JOINs, but the documentation explains how you can include other collections in your query like this:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; $ &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; addresses INCLUDE countries &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; country&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The docs include more in-depth info on &lt;a href=&quot;https://www.litedb.org/docs/dbref/&quot;&gt;how the references between collections work&lt;/a&gt;. It&#39;s a bit more work than defining foreign keys in traditional SQL databases like SQLite.&lt;/p&gt;
&lt;p&gt;From my experience with Mongo, when you need referential integrity that you can take to the bank, you need a proper SQL database. For many schemaless (or rather, schema-loose) scenarios, the document approach works just fine.&lt;/p&gt;
&lt;h3 id=&quot;programming-your-app-with-litedb&quot; tabindex=&quot;-1&quot;&gt;Programming your app with LiteDB &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-07/trying-out-litedb/#programming-your-app-with-litedb&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;How do you code your persistence layer using LiteDB?&lt;/p&gt;
&lt;p&gt;You don&#39;t need DTOs (Data Transfer Objects). Instead of dummy classes, you can &lt;a href=&quot;https://github.com/mbdavid/LiteDB/wiki/BsonDocument&quot;&gt;work directly with documents&lt;/a&gt; like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; address &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;BsonDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
address&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;street&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Palm Street&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
address&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;houseNumber&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
address&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Venice&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
address&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;state&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;CA&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; db &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;LiteDatabase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;database.db&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; col &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;addresses&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// Insert the document into the database. This will create the collection if it does not exist yet.&lt;/span&gt;
    col&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;address&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ... do some more stuff&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Look up your document&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; dbAddress &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; col&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;FindOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;$.city = &#39;Venice&#39;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, it&#39;s super straightforward to work with &amp;quot;raw&amp;quot; instances of &lt;a href=&quot;https://www.litedb.org/docs/data-structure/&quot;&gt;BsonDocument&lt;/a&gt;, which itself is like a Dictionary of a &lt;code&gt;string&lt;/code&gt; and one of BSON types.&lt;/p&gt;
&lt;p&gt;Or, you can use classes and LINQ to create and manipulate your data in a strongly typed fashion:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Address&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; Street &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; HouseNumber &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; City &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; db &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;LiteDatabase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;Addressbook.db&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; col &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;GetCollection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Address&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;addresses&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; address &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Address&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      Street &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Palm Street&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      HouseNumber &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      City &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Venice&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      State &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;CA&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    col&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;address&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// ... do more stuff&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Look up your document&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; dbAddress &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; col&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;FindOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;City &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Venice&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Starting with version 5, you can annotate a class constructor with &lt;code&gt;[BsonCtor]&lt;/code&gt; to tell LiteDB how to construct your class instance. Its properties then no longer need public setters:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Address&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;ObjectId&lt;/span&gt; AddressId &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; Street &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; HouseNumber &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; City &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; State &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;DateTime&lt;/span&gt; CreateDate &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; StreetAddress &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;Street&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;HouseNumber&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Address&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; street&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; houseNumber&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; city&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        AddressId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Guid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewGuid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        Street &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; street&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        HouseNumber &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; houseNumber&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        City &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; city&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        State &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        CreateDate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;now&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;BsonCtor&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;- here&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Address&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ObjectId&lt;/span&gt; addressId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; street&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; houseNumber&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; city&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DateTime&lt;/span&gt; createDate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        AddressId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; addressId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        Street &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; street&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        HouseNumber &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; houseNumber&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        City &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; city&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        State &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        CreateDate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; createDate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, you tell LiteDB how to construct your domain object without having to use a DTO. That&#39;s a big win in my book.&lt;/p&gt;
&lt;p&gt;Alternatively, there is a fluent API. This means you can avoid having this database-specific constructor, which does not belong in the domain. Instead, you tell LiteDB how to construct the domain object somewhere in your infra/persistence layer:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; mapper &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BsonMapper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Global&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

mapper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Entity&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Address&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TAddressId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// set your document ID&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Ignore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StreetAddress&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ignore this property (do not store)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CreateDate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;CreatedAt&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// rename a field&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a complete reference, &lt;a href=&quot;https://www.litedb.org/docs/object-mapping/&quot;&gt;look here&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;which-is-better-for-.net-developers---litedb-or-sqlite%3F&quot; tabindex=&quot;-1&quot;&gt;Which is better for .NET developers - LiteDB or SQLite? &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-07/trying-out-litedb/#which-is-better-for-.net-developers---litedb-or-sqlite%3F&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There is no definite answer to this question. Other than &lt;em&gt;it depends&lt;/em&gt;, of course.&lt;/p&gt;
&lt;p&gt;In most cases, you can&#39;t go wrong with SQLite. You may think that having a schema is too constraining, but in the end, you&#39;ll be glad you have a schema once you&#39;ve figured out the right model for your domain.&lt;/p&gt;
&lt;p&gt;Then again, LiteDB can be just as welcoming to your domain objects. And, it can be more friendly to you while you are mapping it out.&lt;/p&gt;
&lt;p&gt;No need to worry about migrations, for example. Assuming your persistence layer is forgiving enough about missing fields and such.&lt;/p&gt;
&lt;p&gt;I would say that in the end, it&#39;s almost a question of esthetic preferences. Both databases will do a great job where they perform best, which would primarily be a desktop or mobile app.&lt;/p&gt;
&lt;p&gt;There, the trade-offs are very much different compared to a web app that may receive updates several times a day and is being used by dozens of hundreds or users concurrently.&lt;/p&gt;
&lt;p&gt;And if the .NET-native API of LiteDB appeals to you, why not give it a shot? Chances are indeed very good that it will delight you!&lt;/p&gt;
&lt;hr /&gt;
&lt;div id=&quot;featured&quot;&gt;
&lt;h3 id=&quot;update-october-2022&quot; tabindex=&quot;-1&quot;&gt;UPDATE October 2022 &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-04-07/trying-out-litedb/#update-october-2022&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Would you like to dig deeper into the subject?&lt;/p&gt;
&lt;p&gt;I have released a follow-up e-book taking an in-depth look at LiteDB and how it performs.&lt;/p&gt;
&lt;p&gt;It has many more code examples, more details on the API usage, and features a performance shoot-out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://tomaskohl.gumroad.com/l/litedb-handbook&quot;&gt;Grab your copy now!&lt;/a&gt;&lt;/strong&gt; It&#39;s free (or you can pay what you want).&lt;/p&gt;
&lt;/div&gt;
&lt;hr /&gt;
</content>
	</entry>
	
	<entry>
		<title>Are NanoVMs the future?</title>
		<link href="https://tomaskohl.com/code/2020-03-22/are-nanovms-the-future/" />
		<updated>2020-03-22T11:33:00Z</updated>
		<id>https://tomaskohl.com/code/2020-03-22/are-nanovms-the-future/</id>
		<content
      type="html"
    >&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/pulse/containers-future-ian-eyberg/&quot;&gt;This brilliant take-down&lt;/a&gt; of the containers ecosystem made quite a splash yesterday, with the &lt;a href=&quot;https://news.ycombinator.com/item?id=22628274&quot;&gt;predicably heated discussion on HN&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The author&#39;s key point was that containers are ihnerently difficult - if not impossible - to secure - and that we need new approach, one that is breaking from the outdated concept of multi-user, multi-process operating systems we have today that do not belong in the cloud.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com/reply?id=22630458&amp;amp;goto=item%3Fid%3D22628274%2322630458&quot;&gt;This reply&lt;/a&gt; from the commenter &amp;quot;api&amp;quot; summarizes the situation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&#39;ve been saying for years that containers are nothing more than an ugly hack to get around the fact that OSes are broken. More specifically modern OSes still carry 1980s assumptions about multi-tenancy, system administration, application management, state management, etc.&lt;/p&gt;
&lt;p&gt;In the 1980s a &amp;quot;server&amp;quot; was a big expensive special snowflake. Each server had a &amp;quot;system administrator.&amp;quot; The system administrator would &amp;quot;install&amp;quot; software &amp;quot;on&amp;quot; the machine.&lt;/p&gt;
&lt;p&gt;I quoted all those words to highlight no longer valid concepts.&lt;/p&gt;
&lt;p&gt;Boxes are ephemeral now. Systems have more than one administrator, sometimes belonging to a different organization or department. Applications are not installed &amp;quot;on&amp;quot; the system in the sense that they root into it and modify its state. If they do this makes them a terrible hassle to manage. Applications should be ephemeral things that can float freely between machines and store their state in some kind of distributed or remote data store or database, or at least can be easily migrated between hosts. Everything is mobile. Everything is commodity. Everything is liquid.&lt;/p&gt;
&lt;p&gt;OSes are just not designed this way, so we have an ugly hack called a container that basically amounts to &amp;quot;let&#39;s tar up images of operating systems and just treat them like giant static binaries.&amp;quot; Onto this is bolted a whole array of other ugly hacks to make that easy to deal with, but the concept is fundamentally ugly as hell.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&#39;ll admit my expertise in information security isn&#39;t solid enough to evaluate the argument on its technical merits. During development, containers are a God-send in that I can quickly spin-up dependencies without going through a lengthy install-and-configure process. In production, I&#39;ll take whatever approach offers the best security given the requirements.&lt;/p&gt;
&lt;p&gt;The author is a CEO at &lt;a href=&quot;https://nanovms.com/&quot;&gt;NanoVMs&lt;/a&gt;, and he claims that the answer are unikernels and micro/nano VMs where your application is the only thing that&#39;s running.&lt;/p&gt;
&lt;p&gt;I don&#39;t have a problem with the obvious bias his role brings to his argument; I&#39;ll take a report from the trenches with a lot more seriousness than one from an Ivory Tower journalist.&lt;/p&gt;
&lt;p&gt;Interested, I looked around to see if I can try this tech at home for personal education.&lt;/p&gt;
&lt;h3 id=&quot;first-steps-with-nanos&quot; tabindex=&quot;-1&quot;&gt;First steps with Nanos &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-03-22/are-nanovms-the-future/#first-steps-with-nanos&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;From the &lt;a href=&quot;https://github.com/nanovms/nanos&quot;&gt;README&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nanos is a new kernel designed to run one and only one application in a virtualized environment. It has several constraints on it compared to a general purpose operating system such as Windows or Linux - namely it&#39;s a single process system with no support for running multiple programs nor does it have the concept of users or remote administration via ssh.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I am not going to be hacking on Nanos but instead use it for a toy project and see if I can get it running locally.&lt;/p&gt;
&lt;p&gt;To that end, README tells me to use the &lt;a href=&quot;https://ops.city/&quot;&gt;Ops&lt;/a&gt; orchestrator to run my application.&lt;/p&gt;
&lt;p&gt;There, I open the &lt;a href=&quot;https://nanovms.gitbook.io/ops/getting_started&quot;&gt;Getting Started&lt;/a&gt; guide and find out that Ops currently supports only *nix systems. Does my WSL2 count as one? I&#39;ll find out.&lt;/p&gt;
&lt;p&gt;First, I install &lt;code&gt;qemu&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; qemu&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I trust Ops to do the right thing and install it from the web:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; https://ops.city/get.sh &lt;span class=&quot;token parameter variable&quot;&gt;-sSfL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It downloads and install into my user&#39;s directory. I source the &lt;code&gt;.bashrc&lt;/code&gt; file to put its install location into my $PATH.&lt;/p&gt;
&lt;p&gt;Time to run the first app! I copy-and-paste the example from the homepage:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; http &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;http&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeHead&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/plain&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Hello World\n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8083&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Server running at http://127.0.0.1:8083/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I try running it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ops load node_v12.13.0 &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8083&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt; hi.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oops:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;booting /home/tomas/.ops/images/node.img &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
qemu-system-x86_64: warning: TCG doesn&lt;span class=&quot;token string&quot;&gt;&#39;t support requested feature: CPUID.01H:ECX.vmx [bit 5]
assigned: 10.0.2.15
buffer.js:573
    slice: (buf, start, end) =&gt; buf.utf8Slice(start, end),
                                    ^

RangeError: Index out of range
    at Object.slice (buffer.js:573:37)
    at Buffer.toString (buffer.js:770:14)
    at Object.readFileSync (fs.js:386:41)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:972:22)
    at Module.load (internal/modules/cjs/loader.js:812:32)
    at Function.Module._load (internal/modules/cjs/loader.js:724:14)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1025:10)
    at internal/main/run_main_module.js:17:11 {
  code: &#39;&lt;/span&gt;ERR_OUT_OF_RANGE&#39;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; status &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not going to debug this now, I look up what other versions of Node are supported on Nanos via &lt;code&gt;ops pkg list&lt;/code&gt; and try &lt;code&gt;11.5.0&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;booting /home/tomas/.ops/images/node.img &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
qemu-system-x86_64: warning: TCG doesn&#39;t support requested feature: CPUID.01H:ECX.vmx &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;bit &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
assigned: &lt;span class=&quot;token number&quot;&gt;10.0&lt;/span&gt;.2.15
Server running at http://127.0.0.1:8083/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I navigate to &lt;code&gt;localhost:8083&lt;/code&gt;, and indeed, &amp;quot;Hello World&amp;quot; it says in my browser. Neat!&lt;/p&gt;
&lt;p&gt;WSL is a marvel. I can experiment with tech that is Linux-only without having to dual-boot or fight with Virtualbox. I will now try to run a more sophisticated app on Nanos and report back what I find.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Ideal remote working setup for a mature introvert</title>
		<link
      href="https://tomaskohl.com/code/2020-03-06/ideal-remote-working-setup-for-mature-introvert/"
    />
		<updated>2020-03-06T12:20:00Z</updated>
		<id
    >https://tomaskohl.com/code/2020-03-06/ideal-remote-working-setup-for-mature-introvert/</id>
		<content
      type="html"
    >&lt;p&gt;I&#39;ve freelanced for over 10 years now, most of which I spent in my clients&#39; offices.&lt;/p&gt;
&lt;p&gt;That began to change, and I now work primarily from my home with occasional travel to in-person meetings.&lt;/p&gt;
&lt;p&gt;The change was intentional and I prepared for it, both mentally and technically. I think you should, too, if you contemplate a move to remote working.&lt;/p&gt;
&lt;p&gt;COVID-19 notwithstanding, it seems like the industry has been under transformation to more flexible working models anyway. Whether or not this health scare will accelerate the process, it pays to be prepared.&lt;/p&gt;
&lt;p&gt;Here&#39;s what works for me.&lt;/p&gt;
&lt;h3 id=&quot;pre-requisites&quot; tabindex=&quot;-1&quot;&gt;Pre-requisites &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-03-06/ideal-remote-working-setup-for-mature-introvert/#pre-requisites&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I am going to assume a few things about you.&lt;/p&gt;
&lt;p&gt;You are either freelancing or working within an organization that trusts you. While this article outlines how to create the environment for productivity at home, it does not address negotiating terms with your employer. You&#39;re going to have to resolve any trust issues first.&lt;/p&gt;
&lt;p&gt;Second, I&#39;m assuming you are an experienced professional able to work independently. If you are just starting out, doing it completely remotely may not be the right move for you. I, for one, have benefited immensely by being able to rely on the physical support network of peers and mentors when entered the industry.&lt;/p&gt;
&lt;p&gt;Lastly, I&#39;m assuming you are able to control the physical environment around you. That includes being able to declare a portion of your house or apartment as your office, furnishing it according to the needs of your work, creating and maintaining &amp;quot;office hours&amp;quot;, making the necessary purchases, etc.&lt;/p&gt;
&lt;p&gt;This is hard to overstate! Unless you live alone, you are going to negotiate with your spouse or any other humans sharing your living environment.&lt;/p&gt;
&lt;h3 id=&quot;the-importance-of-mindset&quot; tabindex=&quot;-1&quot;&gt;The importance of mindset &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-03-06/ideal-remote-working-setup-for-mature-introvert/#the-importance-of-mindset&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let&#39;s start inside your own head.&lt;/p&gt;
&lt;p&gt;Don&#39;t worry, I am not a &amp;quot;mindset consultant&amp;quot; :) I don&#39;t want to change anything, just let you ask yourself a couple of things. And, instead of telling you what to think, I&#39;ll just share my own response.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;How comfortable are you spending hours and hours alone, in general?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My response: I am naturally introverted and like it when I am alone in a room for a few hours when I have things to do. A prolonged solitude would be uncomfortable, though. I like to space my day with pleasant interactions with people I like.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;How well and for how long can you focus on the task at hand when nobody is looking?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My response: that&#39;s the only way I can focus. On the contrary, when I have a feeling that people could peer over my shoulder anytime, I feel vulnerable and disconcerted.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;How well are you able to set and enforce boundaries between your work and the rest of your life?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My response: I don&#39;t have to discipline myself, because I was born lazy and never suffered from the ambition to work 80+ hours a week. When out with friends or my spouse, I don&#39;t check my work-related e-mails or Slack. And, when working, I refuse non-work interruptions to distract me. Either way, I am trying to be mindful of what I am doing at each particular moment.&lt;/p&gt;
&lt;p&gt;Examine your responses, if you will. Are your answers compatible with being at your home office most of the time, interacting with your peers or clients over Skype only, and moving seamlessly between the &amp;quot;work mode&amp;quot; and &amp;quot;free-time mode&amp;quot; without sacrificing either your work results or the quality of your life?&lt;/p&gt;
&lt;p&gt;Counter-indications: you like the physical company of peers, you like bouncing ideas off each other regularly or just shooting the shit at the water cooler, you get weirded out when alone for a few hours, or you need a boss around to make you lift a finger, so to speak.&lt;/p&gt;
&lt;p&gt;Only you know.&lt;/p&gt;
&lt;h3 id=&quot;negotiating-your-living-arrangement&quot; tabindex=&quot;-1&quot;&gt;Negotiating your living arrangement &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-03-06/ideal-remote-working-setup-for-mature-introvert/#negotiating-your-living-arrangement&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This section applies to you if you share your living space with another human, e.g. your spouse.&lt;/p&gt;
&lt;p&gt;You are about to transform this space into a mixed-use territory. Some of it stays dedicated to leisure but a significant portion is now your office.&lt;/p&gt;
&lt;p&gt;In my case, about a quarter of our living room is my office. It&#39;s where the TV would be. Thankfully, my 43&amp;quot; monitor can also fit that need when we want to watch Netflix.&lt;/p&gt;
&lt;p&gt;Regardless of how much space you fill, that room becomes an office when you work. You &lt;strong&gt;will&lt;/strong&gt; impose limits on what others can do there while in the working mode.&lt;/p&gt;
&lt;p&gt;Potential impacts: your spouse cannot have a friend come over and talk over coffee in this room, your children cannot play there while you work, and when you have a conference call, any other occupants must be quiet.&lt;/p&gt;
&lt;p&gt;Then there&#39;s your presence in itself.&lt;/p&gt;
&lt;p&gt;If your partner works outside your home and you remain home-bound, requests to &amp;quot;take care of stuff&amp;quot; might arrive. You are always at home, after all.&lt;/p&gt;
&lt;p&gt;No, actually you are at work, it just happens that your workplace is also your home.&lt;/p&gt;
&lt;p&gt;Let&#39;s say your partner also works at home while you used to work outside. Now you are dealing with the constant presence of each other. Hopefully your relationship is great but this is an impactful change nonetheless!&lt;/p&gt;
&lt;p&gt;Well, I am not a &amp;quot;relationship consultant&amp;quot; either, and so you&#39;ll have to figure this one out for yourself.&lt;/p&gt;
&lt;p&gt;For me and my partner, taking a few hours apart from each other daily works really well. I do have a need to take a walk, go to the gym, or do something outside every day in any case.&lt;/p&gt;
&lt;p&gt;Expect this negotiation to take some time and do it in good faith.&lt;/p&gt;
&lt;h3 id=&quot;staying-in-touch&quot; tabindex=&quot;-1&quot;&gt;Staying in touch &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-03-06/ideal-remote-working-setup-for-mature-introvert/#staying-in-touch&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This section applies to people working inside a team. If you are a lone-wolf freelancer and you only communicate with your clients, chances are that you have already figured this out.&lt;/p&gt;
&lt;p&gt;Let&#39;s assume the worst possible starting position: your team was sent home without any prior remote-working setup having been put in place.&lt;/p&gt;
&lt;p&gt;You&#39;ve just declared the dining table your office and put your laptop there.&lt;/p&gt;
&lt;p&gt;You are going to have to coordinate with your team-mates daily and keep your sanity. What works, and what doesn&#39;t?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;E-mail and chat&lt;/strong&gt; - those work great when everyone is remote, and I don&#39;t get the occasional complaints about &amp;quot;Slack-overflow&amp;quot;. It takes some discipline to mute Slack&#39;s incessant notifications but it can be done.&lt;/p&gt;
&lt;p&gt;What &lt;em&gt;must&lt;/em&gt; be done is managing the (a)synchronicity. What are the expected response times? Chat is terrible when used synchronously. That said, if you depend a lot on collaboration from others, getting to a shared agreement on each other&#39;s responsiveness is important.&lt;/p&gt;
&lt;p&gt;This topic extends way beyond remote working as email and chat are used inside onsite teams as well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video conferencing&lt;/strong&gt; - awesome invention that never works 100%. Expensive corporate setups are the worst. Try them all and decide on which one sucks the least. I hear good things about Zoom but haven&#39;t tried it yet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ticketing system&lt;/strong&gt; - I like this one the best. If your team already uses something like Jira (the worst piece of garbage on planet Earth but way better than no ticketing system at all), great - use it for &lt;em&gt;everything&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;Need someone to review your PR? Give them a ticket.&lt;/p&gt;
&lt;p&gt;Got stuck with a bug-fix? Create a ticket for your senior colleague.&lt;/p&gt;
&lt;p&gt;Want a... you get the point.&lt;/p&gt;
&lt;p&gt;A well-written ticket has a textual description of what you need as well as all the why&#39;s and when&#39;s. You leave it with the assignee who is a mature adult just like you and will handle it with the proper importance. It&#39;s asynchronicity at its best.&lt;/p&gt;
&lt;p&gt;Do what you can to avoid synchronous tools in favor of asynchronous.&lt;/p&gt;
&lt;h3 id=&quot;the-tech&quot; tabindex=&quot;-1&quot;&gt;The tech &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-03-06/ideal-remote-working-setup-for-mature-introvert/#the-tech&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;OK, now for the fun part.&lt;/p&gt;
&lt;p&gt;Actually, it is going to be rather brief.&lt;/p&gt;
&lt;p&gt;You probably already have a laptop for work. What you really need to add are two things: a desk and a good chair.&lt;/p&gt;
&lt;p&gt;Any desk will do but a good chair is an investment that will repay itself many times over, measured in units of health.&lt;/p&gt;
&lt;p&gt;Unless you work for a unicorn company (I heard many in the US are), your work chair is most likely shitty.&lt;/p&gt;
&lt;p&gt;Don&#39;t treat yourself as your employer treats you, and buy yourself a quality ergonomic chair. &lt;a href=&quot;http://old.prowork.cz/cz/zdravotni-ergonomicke-zidle/therapia-ibody-xl_61/9760_537.html&quot;&gt;Mine&lt;/a&gt; lets me stay put at my desk for the whole day without any back pain; its lower back support is fabulous (link in Czech only).&lt;/p&gt;
&lt;p&gt;Considering your desk, I strongly favor motorized standing desks. &lt;a href=&quot;http://www.exner.cz/en/exvizit/height-adjustable-desk-180-x-80-vp2-180#imos:BC50040066-000569A6A7519A0000A0000A0000A0A0A0A0000A0000&quot;&gt;Mine&lt;/a&gt; has just two buttons, up and down, and that&#39;s good enough, no need for presets - I just stand up, push and hold the button, place my hand on the table and wait until it raises to the comfortable position.&lt;/p&gt;
&lt;p&gt;I can only work standing for a maximum of 20-30 minutes but doing it a few times each day works wonders. A human body did not evolve to be in one position most of the time.&lt;/p&gt;
&lt;h3 id=&quot;other-resources&quot; tabindex=&quot;-1&quot;&gt;Other resources &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-03-06/ideal-remote-working-setup-for-mature-introvert/#other-resources&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&#39;ve outlined the things that are important to me in my personal working and life situation.&lt;/p&gt;
&lt;p&gt;What works for me may or may not be relevant to you. There are many other sources to visit.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.hanselman.com/blog/LoveInATimeOfCoronaVirusTipsTricksAndBestPracticesForWorkingRemotely.aspx&quot;&gt;Scott Hanselman&lt;/a&gt; published a thoughtful article on his remote working setup. He&#39;s got a lot more tech topics covered as well as many, many additional tips.&lt;/p&gt;
&lt;p&gt;I heard good things about the book &lt;strong&gt;Remote - office not required&lt;/strong&gt; by the makers of Basecamp.&lt;/p&gt;
&lt;p&gt;At the end of the day, though, success in remote working does come down to two major pre-requisites: your mature mindset and the culture of your company or team that you can hopefully co-create.&lt;/p&gt;
&lt;p&gt;These days, companies are taking a hard look at remote working. Use it to your advantage.&lt;/p&gt;
&lt;p&gt;Managers who are used to face time will have to re-program themselves. Help them. Educate yourself and promote healthy asynchronous communication that enables remote teams to function well.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Using Nuget packages with .NET Interactive</title>
		<link
      href="https://tomaskohl.com/code/2020-02-11/dotnet-interactive-using-nuget-packages/"
    />
		<updated>2020-02-11T07:00:00Z</updated>
		<id
    >https://tomaskohl.com/code/2020-02-11/dotnet-interactive-using-nuget-packages/</id>
		<content
      type="html"
    >&lt;p&gt;&lt;a href=&quot;https://tomaskohl.com/code/2020-02-07/first-steps-with-dotnet-interactive/&quot;&gt;Last time I installed Jupyter with .NET&lt;/a&gt; and tried to run a few commands to get a feel for what it&#39;s like.&lt;/p&gt;
&lt;p&gt;Before starting today&#39;s session and exploring how I could use Nuget packages, I wondered about the limits of Jupyter / .Net Interactive. Specifically, what kind of code I can execute.&lt;/p&gt;
&lt;p&gt;Does it run within a sandbox?&lt;/p&gt;
&lt;p&gt;At first glance, it does not appear to do so.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tomaskohl.com/code/2020-02-11/hello-c-drive.png&quot; alt=&quot;Hello, C Drive!&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is fine on my PC where I am the only one responsible for what code gets executed, not so much if I wanted to host a Jupyter notebook online.&lt;/p&gt;
&lt;h3 id=&quot;nuget-packages-in-a-notebook&quot; tabindex=&quot;-1&quot;&gt;Nuget packages in a notebook &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-02-11/dotnet-interactive-using-nuget-packages/#nuget-packages-in-a-notebook&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The docs for .NET Interactive are sparse as of today. I&#39;m exploring it using a few &lt;a href=&quot;https://github.com/dotnet/interactive/tree/master/NotebookExamples/csharp/Docs&quot;&gt;samples&lt;/a&gt; that are well hidden inside the repo, specifically a sample for &lt;a href=&quot;https://github.com/dotnet/interactive/blob/master/NotebookExamples/csharp/Docs/Importing%20packages.ipynb&quot;&gt;Importing packages, libraries, and scripts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&#39;s say we want to play around with &lt;a href=&quot;https://github.com/mono/taglib-sharp&quot;&gt;taglib-sharp&lt;/a&gt;. First, we reference it:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;#r &amp;quot;nuget: TagLibSharp, 2.2.0&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In the next cell, we can import its namespace, instantiate its classes and invoke their methods.&lt;/p&gt;
&lt;p&gt;First, let&#39;s expand the original &lt;code&gt;Song&lt;/code&gt; class to work with more metadata attributes and make TagLib a dependency.&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Song&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Song&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TagLib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Tag&lt;/span&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        _tag &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TagLib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Tag&lt;/span&gt; _tag&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; Title &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; _tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Title&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; Artist &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;, &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Performers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; Genre &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;, &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Genres&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rest is straightforward:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; tag &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; TagLib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;File&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;C:\Users\Tomas\Music\Library\Juan D&#39;Arienzo\Todo de Juan 5\Ansiedad 12562-1_RP.flac&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Tag&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; song &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Song&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;song&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lo and behold, the code can access my song and read its metadata:&lt;br /&gt;
&lt;img src=&quot;https://tomaskohl.com/code/2020-02-11/using-taglib-sharp-in-dotnet-interactive.png&quot; alt=&quot;Exploring TagLibSharp in .NET Interactive&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Very cool.&lt;/p&gt;
&lt;p&gt;&lt;s&gt;In the next installment, I will look at my hosting options, what the security boundaries are, and what it means for deployment.&lt;/s&gt;&lt;br /&gt;
PS I reviewed &lt;a href=&quot;https://jupyter-notebook.readthedocs.io/en/stable/public_server.html&quot;&gt;Jupyter docs&lt;/a&gt; for public server installation, which is single-user and not really what I meant by &amp;quot;hosting&amp;quot;.&lt;/p&gt;
&lt;p&gt;The docs led me to &lt;a href=&quot;https://jupyterhub.readthedocs.io/en/latest/&quot;&gt;JupyterHub&lt;/a&gt;, which seems appropriate for class-room use. From what I was able to understand, it&#39;s still not meant for a wider public use (aka, &amp;quot;come to my site and play with my notebooks, anonymously&amp;quot;). For even more users, there&#39;s &lt;a href=&quot;https://zero-to-jupyterhub.readthedocs.io/en/latest/&quot;&gt;Jupyter on Kubernetes&lt;/a&gt; - I know Docker and k8s well enough to not want to pay for the infrastructure if all I wanted to do was to expose a little playground.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dotnet/interactive/blob/master/docs/CreateBinder.md&quot;&gt;Microsoft&#39;s own docs&lt;/a&gt; talk about &lt;a href=&quot;https://mybinder.org/&quot;&gt;Binder&lt;/a&gt;, which takes the pain (and cost) of Docker deployment out of your hands. As much as I wanted to do this on my own, that&#39;s what I would probably use.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>First steps with .NET Interactive</title>
		<link
      href="https://tomaskohl.com/code/2020-02-07/first-steps-with-dotnet-interactive/"
    />
		<updated>2020-02-07T16:20:00Z</updated>
		<id
    >https://tomaskohl.com/code/2020-02-07/first-steps-with-dotnet-interactive/</id>
		<content
      type="html"
    >&lt;p&gt;Microsoft has recently announced support for C# and F# in Jupyter Notebooks. On February 6, they &lt;a href=&quot;https://devblogs.microsoft.com/dotnet/net-interactive-is-here-net-notebooks-preview-2/&quot;&gt;announced a new release&lt;/a&gt;, Preview 2, with some naming and organizational changes.&lt;/p&gt;
&lt;p&gt;Having recently listened to an &lt;a href=&quot;https://www.mergeconflict.fm/187&quot;&gt;episode of the Merge Conflict podcast&lt;/a&gt; where Frank Krueger spoke quite affectionately about Notebooks, I decided to give this a try.&lt;/p&gt;
&lt;p&gt;To be sure, I have never experimented with Jupyter and my knowledge of Python is superficial at best. That is why I&#39;ve only got to it now when I can use C# with Jupyter.&lt;/p&gt;
&lt;h3 id=&quot;installation-experience&quot; tabindex=&quot;-1&quot;&gt;Installation experience &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-02-07/first-steps-with-dotnet-interactive/#installation-experience&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I decided to follow the first steps for the &amp;quot;dotnet/interactive&amp;quot; experience.&lt;/p&gt;
&lt;p&gt;The installation of Anaconda took quite some time on my Ryzen 3950X desktop. It spent most of the time initializing its package cache or something.&lt;/p&gt;
&lt;p&gt;The installation of &lt;code&gt;dotnet global&lt;/code&gt; tool &lt;code&gt;Microsoft.dotnet-interactive&lt;/code&gt; and  a .NET kernel for Anaconda went by in a split second.&lt;/p&gt;
&lt;p&gt;So far so good.&lt;/p&gt;
&lt;h3 id=&quot;running-jupyter-locally-with-.net&quot; tabindex=&quot;-1&quot;&gt;Running Jupyter locally with .NET &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-02-07/first-steps-with-dotnet-interactive/#running-jupyter-locally-with-.net&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I continued with a &lt;a href=&quot;https://github.com/dotnet/interactive/blob/master/docs/NotebooksLocalExperience.md&quot;&gt;tutorial&lt;/a&gt; from Microsoft&#39;s dotnet Github repo.&lt;/p&gt;
&lt;p&gt;I launched the Anaconda Navigator, which is a Windows app, and from there I chose to run a Jupyter Notebook.&lt;/p&gt;
&lt;p&gt;A browser windows appeared. Bummer, a web app.&lt;/p&gt;
&lt;p&gt;I created a new file. Can I do a Hello World here? Sure I can!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tomaskohl.com/code/2020-02-07/hello-world.png&quot; alt=&quot;Hello world, in Jupyter with C#&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Following the next example, I define a new class, instantiate it and peek at its contents.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tomaskohl.com/code/2020-02-07/new-song.png&quot; alt=&quot;Looking at an object&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Nice!&lt;/p&gt;
&lt;p&gt;Each box I create, called a &amp;quot;cell&amp;quot;, can contain either code or markdown. Therefore, I can create an interactive documentation, for instance for a consumer of my C# library, can I?&lt;/p&gt;
&lt;p&gt;I&#39;d expect that a markdown cell will get rendered as HTML. It does, sort-of. Out of the box, it does not support smileys, for example. &lt;a href=&quot;https://stackoverflow.com/questions/50197537/jupyter-notebook-does-not-support-smileys-in-markdown-cells&quot;&gt;This SO answer&lt;/a&gt;, however, provided a way: just copy-and-paste it! 😉&lt;/p&gt;
&lt;p&gt;This is how the notebook looks like now with markdown content included:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tomaskohl.com/code/2020-02-07/notebook-with-markdown.png&quot; alt=&quot;Notebook with code and markdown&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is indeed very intriguing. Next, I am going to explore what other code I can run there. For instance, can I reference Nuget packages? Load DLLs?&lt;/p&gt;
&lt;p&gt;Also, I&#39;d be keen to see how I can host this notebook for the consumers of my library. I heard that I can do so directly on Github, so I am going to see about that.&lt;/p&gt;
&lt;p&gt;To be continued!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>The amazing (and dangerous!) switch expressions in C# 8</title>
		<link
      href="https://tomaskohl.com/code/2020-02-05/the-switch-expressions-in-csharp-8/"
    />
		<updated>2020-02-05T15:37:00Z</updated>
		<id
    >https://tomaskohl.com/code/2020-02-05/the-switch-expressions-in-csharp-8/</id>
		<content
      type="html"
    >&lt;p&gt;I revisited my tentative understanding of &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/csharp/pattern-matching&quot;&gt;the new pattern matching tools in C# 8&lt;/a&gt; when it made rounds on &lt;a href=&quot;https://news.ycombinator.com/item?id=22224277&quot;&gt;Hacker News&lt;/a&gt; today.&lt;/p&gt;
&lt;p&gt;Given that C# started out as a general purpose language within the OOP lineage, it&#39;s interesting to see it adopt some of the features made popular by the functional community.&lt;/p&gt;
&lt;p&gt;What it also does, though, is to place seeds of doubt in the mind of a curious programmer.&lt;/p&gt;
&lt;p&gt;What is this functional stuff? How far can I go with it until my peers will start declining my PRs because they can no longer grok my intent from the code?&lt;/p&gt;
&lt;p&gt;When given a choice between procedural and object-oriented mindset, I choose the latter. Similarly, when comparing procedural and functional, the latter also wins in my mind. Procedural is evil, error-prone, inelegant, even vulgar.&lt;/p&gt;
&lt;p&gt;It gets more complicated if given a choice between object-oriented and functional. In the context of C#, I am predominantly writing OOP code with &lt;em&gt;some&lt;/em&gt; functional patterns mixed-in by way of experimentation.&lt;/p&gt;
&lt;p&gt;Not being particularly well-versed in the functional idioms, I take the occasional learning forays into them when they make appearances in C#.&lt;/p&gt;
&lt;p&gt;Today&#39;s post is about the switch expressions.&lt;/p&gt;
&lt;h3 id=&quot;the-new-switch-expression-in-c%23-8&quot; tabindex=&quot;-1&quot;&gt;The new switch expression in C# 8 &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-02-05/the-switch-expressions-in-csharp-8/#the-new-switch-expression-in-c%23-8&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#switch-expressions&quot;&gt;switch expression&lt;/a&gt; is a very elegant concept that replaces a procedural statement with an expression. So far so good. Or great, actually.&lt;/p&gt;
&lt;h4 id=&quot;switching-over-simple-values&quot; tabindex=&quot;-1&quot;&gt;Switching over simple values &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-02-05/the-switch-expressions-in-csharp-8/#switching-over-simple-values&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Suppose I want to map a numerical score to a textual evaluation. Before, I could write this:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; evaluation&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;score &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    evaluation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;top performer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;score &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    evaluation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;mediocre&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    evaluation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;poor&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// default;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don&#39;t know about you but the word &lt;code&gt;if&lt;/code&gt; always gives me creeps. And what offends me even more is the split between the declaration of initialization of the &lt;code&gt;evaluation&lt;/code&gt; variable. Gross.&lt;/p&gt;
&lt;p&gt;Now, I can write this:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; evaluation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; score &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;score &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;top performer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;score &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mediocre&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _ &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;poor&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// default&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Better: no &lt;strong&gt;ifs&lt;/strong&gt; and the variable is declared and initialized in a single step.&lt;/p&gt;
&lt;p&gt;There&#39;s one limitation I do not like. Suppose I have this silly enum:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Colors&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Red&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Yellow&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Orange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Blue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Black
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s say I want to match a color to a string of either &amp;quot;warm&amp;quot; or &amp;quot;cool&amp;quot;. I would like to be able to do this:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; mood &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; color &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Red&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Yellow&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Orange &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;warm&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    Blue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Black &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cool&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _ &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;undefined&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// or whatever&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, that won&#39;t compile. According to &lt;a href=&quot;https://stackoverflow.com/questions/56676260/c-sharp-8-switch-expression-multiple-cases-with-same-result&quot;&gt;this helpful SO question&lt;/a&gt;, the way to go is:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; mood &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; color &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Colors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Red&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Colors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Yellow&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Colors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Orange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;warm&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Colors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Blue&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Colors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Black&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cool&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _ &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;undefined&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// or whatever&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the need for a new helper variable &lt;code&gt;x&lt;/code&gt; in the switch expression combined with the &lt;code&gt;when&lt;/code&gt; filter. I can live with it but it&#39;s not as elegant as it could&#39;ve been.&lt;/p&gt;
&lt;h4 id=&quot;switching-over-tuples&quot; tabindex=&quot;-1&quot;&gt;Switching over tuples &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-02-05/the-switch-expressions-in-csharp-8/#switching-over-tuples&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;I quite like using tuples as throw-away data carriers used in a private context, inside a method. Their support in modern C# is amazing, and yes, you can now switch over them.&lt;/p&gt;
&lt;p&gt;This example is adapted from the &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#switch-expressions&quot;&gt;Microsoft docs&lt;/a&gt;. Notice the dependency on &lt;a href=&quot;https://github.com/louthy/language-ext&quot;&gt;&lt;strong&gt;LanguageExt.Core&lt;/strong&gt;&lt;/a&gt;, which gives me the wonderful &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt; struct.&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;LanguageExt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;PatternMatchingCs&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hand&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        Rock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Paper&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Scissors
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RockPaperScissors&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Option&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Hand&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Hand&lt;/span&gt; first&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hand&lt;/span&gt; second&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;first&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; second&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Paper&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Paper&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Scissors&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Paper&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rock&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Paper&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Paper&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Scissors&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Scissors&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Scissors&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rock&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Scissors&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Paper&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Hand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Scissors&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Option&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Hand&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;None &lt;span class=&quot;token comment&quot;&gt;// to be interpreted as a tie&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The enum &lt;code&gt;Hand&lt;/code&gt; has three members and the method takes two arguments on this enum type, and so the number of combinations is still manageable.&lt;/p&gt;
&lt;p&gt;I would say that with more enum members and/or more combinations, this feature would quickly cease to be elegant or maintainable. Here, I like it.&lt;/p&gt;
&lt;h4 id=&quot;switching-over-more-complex-objects&quot; tabindex=&quot;-1&quot;&gt;Switching over more complex objects &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-02-05/the-switch-expressions-in-csharp-8/#switching-over-more-complex-objects&quot;&gt; &lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;I&#39;ll follow with the usual examples as seen in the Microsoft docs, and introduce classes &lt;code&gt;Triangle&lt;/code&gt;, &lt;code&gt;Rectangle&lt;/code&gt;, and &lt;code&gt;Circle&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Triangle&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; Base &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; Height &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Rectangle&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; Width &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; Height &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Circle&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; Radius &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that these are basically DTOs, &amp;quot;dumb&amp;quot; classes without any methods. Data carriers.&lt;/p&gt;
&lt;p&gt;Suppose I want to calculate the area. Using the functional way of thinking that decouples data and functionality, the switch expression lets me do this:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AreaOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;/span&gt; param&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; param &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token return-type class-name&quot;&gt;Triangle&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Base &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; T&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Height &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;token return-type class-name&quot;&gt;Rectangle&lt;/span&gt; r &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Width &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Height&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;token return-type class-name&quot;&gt;Circle&lt;/span&gt; c &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Radius &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Radius &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PI&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       _ &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;InvalidArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Unrecognized parameter&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far so good? Not really.&lt;/p&gt;
&lt;h3 id=&quot;the-problems&quot; tabindex=&quot;-1&quot;&gt;The problems &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-02-05/the-switch-expressions-in-csharp-8/#the-problems&quot;&gt; &lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are two things I don&#39;t like about the examples like this one that I see around. (This one is from the &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/csharp/pattern-matching&quot;&gt;official docs on Pattern Matching&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Number one, &lt;code&gt;object param&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We&#39;ve gone a long way from untyped variables of the yesteryear, and are they now coming back?&lt;/p&gt;
&lt;p&gt;When would I want to write a function that can take an arbitrary object as its parameter but can only process a few specific classes? Never, that&#39;s when.&lt;/p&gt;
&lt;p&gt;Which brings me to number two, &lt;code&gt;throw new InvalidArgumentException(...)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is a direct consequence of being flexible on the API surface but actually constrained in the implementation.&lt;/p&gt;
&lt;p&gt;The function promises to return a &lt;code&gt;double&lt;/code&gt; value when given an argument of an &lt;code&gt;object&lt;/code&gt; but will mostly throw exceptions if you consider the billion possible subclasses of &lt;code&gt;object&lt;/code&gt; that you could give it.&lt;/p&gt;
&lt;p&gt;I don&#39;t think that throwing exceptions fits the functional paradigm (the functional folks have the concepts of Option or Either instead). It does fit the OOP mindset, but the &lt;code&gt;object param&lt;/code&gt; doesn&#39;t. So what is this, really?&lt;/p&gt;
&lt;p&gt;Granted, these are only examples of what you could do, not recommendations of what you &lt;em&gt;should&lt;/em&gt; do. And I think that the other examples given therein, such as switching over enums, are sensible. If you must use enums, that is.&lt;/p&gt;
&lt;p&gt;Third, why decouple the functionality from the object? Is a (possibly static) function operating on &amp;quot;dumb&amp;quot; data better than a &amp;quot;smart&amp;quot; class and if so, when and how?&lt;/p&gt;
&lt;p&gt;Before, we were being taught to have an interface like this:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IShape&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Area&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then have &lt;code&gt;Circle&lt;/code&gt;, &lt;code&gt;Triangle&lt;/code&gt;, and &lt;code&gt;Rectangle&lt;/code&gt; implement this interface. By way of this useful abstraction, we then get:&lt;/p&gt;
&lt;pre class=&quot;language-cs&quot;&gt;&lt;code class=&quot;language-cs&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CalculateSomething&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IShape&lt;/span&gt; shape&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; area &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; shape&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Area&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// do something with it and return the result&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That way, any object with an area will implement the interface and will be guaranteed to give me its area. No having to inquire about its type at runtime and deal with exceptions when I cannot determine it.&lt;/p&gt;
&lt;p&gt;Also, no problems in having obsolete switch expressions that do not contain logic for new types I&#39;ve added since writing them.&lt;/p&gt;
&lt;p&gt;When facing a problem like this, in that my system deals with subjects that have some things in common, my esthetic preference goes toward OOP with its polymorphism, with its interfaces, classes that implement them, decorators, the whole deal.&lt;/p&gt;
&lt;h2 id=&quot;elegant-but-dangerous&quot; tabindex=&quot;-1&quot;&gt;Elegant but dangerous &lt;a class=&quot;direct-link&quot; href=&quot;https://tomaskohl.com/code/2020-02-05/the-switch-expressions-in-csharp-8/#elegant-but-dangerous&quot;&gt; &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In summary, these switch expressions look very elegant when used for &amp;quot;primitive&amp;quot; values, fairly good when used for processing tuples, and somewhat questionable when used to fork behavior based on parameter type.&lt;/p&gt;
&lt;p&gt;Their syntax is beautiful and they replace ugly procedural statements with expressions, with all the advantages this implies for code maintainability, testability, and easiness to grok at first read.&lt;/p&gt;
&lt;p&gt;When misused, they let a naive programmer think he&#39;s using functional concepts when clearly that is not the case, such as when breaking promises of function declarations / API and throwing exceptions when not being able to deal with inputs.&lt;/p&gt;
&lt;p&gt;As always, I could be wrong in many ways about this, and will update this post as I learn more about this functionality.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>How to create reports in MongoDB with just some Javascript</title>
		<link
      href="https://tomaskohl.com/code/2020-02-01/mongodb-reports-with-javascript/"
    />
		<updated>2020-02-01T16:20:00Z</updated>
		<id>https://tomaskohl.com/code/2020-02-01/mongodb-reports-with-javascript/</id>
		<content
      type="html"
    >&lt;p&gt;I didn&#39;t appreciate what I had at my disposal when first working with MongoDB.&lt;/p&gt;
&lt;p&gt;It frustrated me by not being an SQL database, by not having joins and transactions. I came to it with the SQL mindset and it did not comply with my expectations.&lt;/p&gt;
&lt;p&gt;It took me a long time working on Express apps in Node.js until I realized, when working on a support ticket, that I could use Javascript directly in Mongo, without having to write a Node app.&lt;/p&gt;
&lt;p&gt;When would I take advantage of that?&lt;/p&gt;
&lt;p&gt;Suppose the customer wants to receive a one-off report of some kind. If you have a Postgres database, you write some SQL. With Mongo, you write some Javascript.&lt;/p&gt;
&lt;p&gt;Let&#39;s consider a simple example. Your marketing wants to receive a list of customers who have placed something in their cart but did not finish the checkout process.&lt;/p&gt;
&lt;p&gt;Let&#39;s say you have two collections: &lt;code&gt;customers&lt;/code&gt; and &lt;code&gt;orders&lt;/code&gt;. The report ought to contain the user&#39;s name, e-mail, and the name of the most valuable item in the shopping cart.&lt;/p&gt;
&lt;p&gt;Furthermore, assume that the items placed into the shopping cart are stored in the &lt;code&gt;orders&lt;/code&gt; collection as sub-documents.&lt;/p&gt;
&lt;p&gt;This is how I would go about it, roughly:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; unfinished &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; reportingPeriodStart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;2020-01-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; orders &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;orders&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;createdAt&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;$gte&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; reportingPeriodStart &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;isPurchased&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; order &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; orders&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; customer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;customers&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;_id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_customer
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  unfinished&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fullName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;best&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; current&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
        current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;price &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; best&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;price
          &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; current
          &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; best&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Name;Email;Item&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;unfinished&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;email&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;\n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;ll save this file somewhere, and run the &lt;code&gt;mongo&lt;/code&gt; client to receive your report:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mongo -u username -p password --quiet mongodb://localhost:27017 yourfile.js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;(COMPLAINT: even with the &lt;code&gt;--quiet&lt;/code&gt; flag, &lt;code&gt;mongo&lt;/code&gt; still prints out useless status information to STDOUT, necesistating further processing with your favorite Linux commandline tool)&lt;/p&gt;
&lt;p&gt;As you can see, you can get around the lack of SQL by writing simple procedural code that can find, filter, sort, and otherwise manipulate the results.&lt;/p&gt;
&lt;p&gt;Personally, I would still very much prefer to write SQL. It&#39;s declarative and you could get the same result as from the above example with less amount of code.&lt;/p&gt;
&lt;p&gt;MongoDB does not have SQL but it has Javascript. It&#39;s not the next-best-thing to SQL but it&#39;s a lot better than nothing at all. I don&#39;t have to write an extra app to get my data out, and that is very good indeed.&lt;/p&gt;
</content>
	</entry>
</feed>
