<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>Joe Plumb - Let&#39;s Do</title>
	<subtitle>I am writing about my experiences as a human being on planet Earth.</subtitle>
	
	<link href="https://joeplumb.com/feed/feed.xml" rel="self"/>
	<link href="https://joeplumb.com/"/>
	<updated>2022-08-20T00:00:00Z</updated>
	<id>https://joeplumb.com/</id>
	<author>
		<name>Joe Plumb</name>
		<email>contact@joeplumb.com</email>
	</author>
	
	<entry>
		<title>Custom event-driven ETL with SQL, Azure Functions, Data Factory and Event Grid</title>
		<link href="https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/"/>
		<updated>2022-08-20T00:00:00Z</updated>
		<id>https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/</id>
		<content type="html">&lt;h2 id=&quot;overview&quot;&gt;Overview &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#overview&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Metadata driven frameworks are very common in ETL scenarios - they make it faster to develop processes and provide more flexibility when processing data in your system. There are lots of ways to build and orchestrate these processes - Azure Data Factory has a &lt;a href=&quot;https://docs.microsoft.com/azure/data-factory/copy-data-tool-metadata-driven&quot;&gt;metadata-driven copy wizard&lt;/a&gt; you can use to quickly build data movement pipelines, Microsoft MVP Paul Andrew has built an &lt;a href=&quot;https://mrpaulandrew.com/2020/08/17/adf-procfwk-v1-8-5-execution-precursor/&quot;&gt;open source metadata framework on top of Data Factory called adf.procfwk&lt;/a&gt;, and many organisations build and maintain their own custom metadata-driven frameworks too.&lt;/p&gt;
&lt;p&gt;I worked on a data warehouse migration project earlier this year which involved modernizing key portions of the workload from SSIS and SQL Server into Azure. This included extending the existing metadata-driven ETL framework to operate in the new cloud architecture. The project was a success and I learned a lot about migrating and modernizing complex data systems; unfortunately end-to-end job performance in the new cloud environment was degraded. We knew what the root cause of this issue was - a mix of insufficient bandwidth between on premises and cloud, and some shortcuts I made during the implementation phase to extract job metadata from the custom framework.&lt;/p&gt;
&lt;p&gt;What were those shortcuts? Without getting too bogged down in the detail, the metadata parsing and job triggering was implemented in a data factory pipeline which, at it&#39;s highest frequency, could only be triggered &lt;em&gt;once every minute&lt;/em&gt;. This was a stark difference to the original system which ran multiple times a second (!). I knew a more lightweight event-driven solution was possible, but ran out of time to implement it in the scope of the project. Naturally, this has been bugging me ever since - so I found some time over the last few weeks to re-visit and solve the problem.&lt;/p&gt;
&lt;h3 id=&quot;solution-architecture&quot;&gt;Solution architecture &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#solution-architecture&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://techcommunity.microsoft.com/t5/azure-sql-blog/introduction-to-azure-sql-bindings-for-azure-functions-data/ba-p/3074172&quot;&gt;Azure SQL bindings for Azure Functions&lt;/a&gt; was announced in preview at the beginning of this year and I always thought this would form part of the solution. Unfortunately you can&#39;t yet trigger a function on updates to a table - only input and output bindings are supported, but that&#39;s enough for a working solution:&lt;/p&gt;
&lt;img class=&quot;img-fluid&quot; src=&quot;https://joeplumb.com/img/custom-event-driven-etl-with-sql-azure-functions-event-grid/1.png&quot; alt=&quot;Image depicting overall solution architecture. Diagram shows &quot; /&gt;
&lt;h4 id=&quot;processing-flow&quot;&gt;Processing flow &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#processing-flow&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Metadata control table(s) with job status managed in SQL DB. SQL Server Agent Job manages job provisioning.&lt;/li&gt;
&lt;li&gt;Azure Functions recurring Timer trigger ensures function polls the SQL table for target jobs event-driven processes.&lt;/li&gt;
&lt;li&gt;Azure Function collects jobs from metadata table in “Started” state, updates state to “processing”, and publishes an event with job metadata to Event Grid topic.&lt;/li&gt;
&lt;li&gt;Azure Data Factory pipeline with custom event trigger is triggered, parsing metadata from inbound event for use in pipeline.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The sample code for the pipeline, function, and database table are &lt;a href=&quot;https://github.com/joe-plumb/sql-azure-functions-adf-event-trigger&quot;&gt;available on GitHub&lt;/a&gt;. For a more detailed runthrough of how to implement this in your own environment, keep reading below!&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#prerequisites&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Local environment&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Microsoft SQL instance&lt;/em&gt; - this can be any flavor of MSSQL. For ease of development in a Linux environment, I recommend running the &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker?view=sql-server-ver16&amp;amp;pivots=cs1-bash&quot;&gt;SQL Server Linux container image with Docker&lt;/a&gt;, but any Microsoft SQL will do.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Azurite Storage Emulator&lt;/em&gt; - similarly, for local development of Azure Functions I recommend the Azurite Storage Emulator container, &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=docker-hub&quot;&gt;also available via container image.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Azure Functions&lt;/em&gt; - for more details on setting up your local environment for Azure Functions development &lt;a href=&quot;https://docs.microsoft.com/azure/azure-functions/functions-develop-local&quot;&gt;check the documentation&lt;/a&gt;.
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Azure Functions Core Tools&lt;/em&gt; - The SQL bindings extension is part of a preview extension bundle (v4.x). See &lt;a href=&quot;https://github.com/Azure/azure-functions-sql-extension/issues/250&quot;&gt;this GitHub issue&lt;/a&gt; for details on updating core tools in your local environment.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Azure Functions Preview&lt;/em&gt; - Ensure that the &lt;code&gt;requirements.txt&lt;/code&gt; of the deployed app specifies &lt;code&gt;azure-functions==1.11.3b1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cloud environment&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Event Grid Topic&lt;/em&gt; - &lt;a href=&quot;https://docs.microsoft.com/azure/event-grid/create-custom-topic#create-a-custom-topic-or-domain&quot;&gt;Create an event grid topic&lt;/a&gt; in your Azure Subscription. Note the &lt;strong&gt;Topic URI&lt;/strong&gt; and &lt;strong&gt;Topic Key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Azure Data Factory&lt;/em&gt; - &lt;a href=&quot;https://docs.microsoft.com/azure/data-factory/quickstart-create-data-factory-portal&quot;&gt;Create an Azure Data Factory&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;sql&quot;&gt;SQL &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#sql&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After starting your database service, you&#39;re ready to create the few objects required for this demo. If you are using the Linux container image, there are some steps you need to go through to update the password after you&#39;ve started the container, all &lt;a href=&quot;https://docs.microsoft.com/sql/linux/quickstart-install-connect-docker?view=sql-server-ver16&amp;amp;pivots=cs1-bash#pullandrun2022&quot;&gt;detailed in the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;create-job-table&quot;&gt;Create Job Table &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#create-job-table&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Connect to your SQL database with &lt;a href=&quot;https://docs.microsoft.com/sql/ssms/quickstarts/ssms-connect-query-sql-server?view=sql-server-ver16&quot;&gt;SSMS&lt;/a&gt; or &lt;a href=&quot;https://docs.microsoft.com/sql/azure-data-studio/quickstart-sql-server?view=sql-server-ver16&quot;&gt;Azure Data Studio&lt;/a&gt;. Execute the below script to create a sample database and job table:&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;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DATABASE&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ToDo &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;JobId&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;JobStepId&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;JobName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; nvarchar&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 operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;JobStepName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; nvarchar&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 operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;JobStepType&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;JobStepStatus&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; nvarchar&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 operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;JobMetadata&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; nvarchar&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 boolean&quot;&gt;null&lt;/span&gt;&lt;br /&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;Insert a sample job&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;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ToDo &lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;JobId&lt;span class=&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;JobStepId&lt;span class=&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;JobName&lt;span class=&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;JobStepName&lt;span class=&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;JobStepType&lt;span class=&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;JobStepStatus&lt;span class=&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;JobMetadata&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3012&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;301201&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Sample Job&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;First step of job&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Started&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;{&quot;jobmetadata&quot;:&quot;examplepayload&quot;}&#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;Take a note of your user ID and password. You&#39;ll need these when setting up the bindings in your Azure Function.&lt;/p&gt;
&lt;h2 id=&quot;azure-function&quot;&gt;Azure Function &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#azure-function&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;create-new-function&quot;&gt;Create new function &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#create-new-function&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Start by creating a new directory, then open this in VS Code. When you&#39;re in your empty directory, open the command palette (&lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;shift&lt;/code&gt; + &lt;code&gt;p&lt;/code&gt;), then find and select &lt;em&gt;Azure Functions: Create Function&lt;/em&gt;. You&#39;ll then have to answer a series of prompts about the new function.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select a &lt;em&gt;language for your function project&lt;/em&gt;: Choose Python.&lt;/li&gt;
&lt;li&gt;Select a &lt;em&gt;Python alias to create a virtual environment&lt;/em&gt;: Choose the location of your Python interpreter. If the location isn&#39;t shown, type in the full path to your Python binary.&lt;/li&gt;
&lt;li&gt;Select a &lt;em&gt;template for your project&#39;s first function&lt;/em&gt;: Choose HTTP trigger. &lt;em&gt;NB: in the initial design above, I recommend use of the timer trigger. For simplicity, we will use the HTTP trigger in this example function.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Provide a &lt;em&gt;function name&lt;/em&gt;: Leave this as HTTPTrigger1.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Authorization level&lt;/em&gt;: Choose Anonymous, which enables anyone to call your function endpoint. You can find more information about &lt;a href=&quot;https://docs.microsoft.com/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=python#authorization-keys&quot;&gt;access keys for functions here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Update &lt;code&gt;__init__.py&lt;/code&gt; with the following code:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; datetime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; uuid&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; azure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;functions &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; func&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;HttpRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobItems&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlRowList&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; outputEvent&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Out&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EventGridOutputEvent&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobItemsUpdate&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Out&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlRow&lt;span class=&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 operator&quot;&gt;&gt;&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;HttpResponse&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    rows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&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;lambda&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loads&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_json&lt;span class=&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; jobItems&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# Update status for inbound jobs&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; rows&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;update&lt;span class=&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;&quot;JobStepStatus&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ADF_Processing&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;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# Set ouputs&lt;/span&gt;&lt;br /&gt;    outputEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EventGridOutputEvent&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uuid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uuid4&lt;span class=&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; subject&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;adf-test-subject&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event_type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;adf-event-1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    data&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;rows&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event_time&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;datetime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;datetime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utcnow&lt;span class=&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; data_version&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;1.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;br /&gt;    jobItemsUpdate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlRowList&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rows&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;HttpResponse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dumps&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rows&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        status_code&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;br /&gt;        mimetype&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;add-input-and-output-bindings&quot;&gt;Add input and output bindings &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#add-input-and-output-bindings&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Update the &lt;code&gt;function.json&lt;/code&gt; file to include the 1 input (from SQL) and 2 output (SQL and Event Grid) bindings. You can learn more about bindings, including supported services in the &lt;a href=&quot;https://docs.microsoft.com/azure/azure-functions/functions-bindings-azure-sql?tabs=in-process%2Cextensionv3&amp;amp;pivots=programming-language-python&quot;&gt;Reference section of the documentation&lt;/a&gt;&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;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;scriptFile&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;__init__.py&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;bindings&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;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;authLevel&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&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;httpTrigger&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;in&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&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;req&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;methods&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;br /&gt;          &lt;span class=&quot;token string&quot;&gt;&quot;get&quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&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;http&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;out&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&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;$return&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&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;jobItems&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&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;sql&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;in&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;commandText&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;select [JobId], [JobStepId],[JobName],[JobStepName],[JobStepType],[JobStepStatus],[JobMetadata] from dbo.ToDo where [JobStepType] = 6 and [JobStepStatus] = &#39;Started&#39; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;commandType&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;connectionStringSetting&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SqlConnectionString&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&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;jobItemsUpdate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&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;sql&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;out&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;commandText&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dbo.ToDo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;connectionStringSetting&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SqlConnectionString&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&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;eventGrid&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&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;outputEvent&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;topicEndpointUri&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;MyEventGridTopicUriSetting&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;topicKeySetting&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;MyEventGridTopicKeySetting&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;out&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the &lt;code&gt;commandText&lt;/code&gt; in the &lt;code&gt;sql&lt;/code&gt; input binding - here we can see the query that will be executed on our database to collect incoming data. In this example, we&#39;re looking for job metadata when the &lt;code&gt;JobStepType = 6&lt;/code&gt; and &lt;code&gt;JobStepStatus = &#39;Started&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;add-secrets-to-local.settings.json&quot;&gt;Add secrets to &lt;code&gt;local.settings.json&lt;/code&gt; &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#add-secrets-to-local.settings.json&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Notice in the &lt;code&gt;function.json&lt;/code&gt; the connection settings you need to add - &lt;code&gt;SqlConnectionString&lt;/code&gt; for the SQL database, &lt;code&gt;MyEventGridTopicUriSetting&lt;/code&gt; and &lt;code&gt;MyEventGridTopicKeySetting&lt;/code&gt; for the Event Grid. If using the Azurite Storage Emulator, you can add the &lt;a href=&quot;https://docs.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio#connection-strings&quot;&gt;known development connection strings&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;quot;SqlConnectionString&amp;quot;: &amp;quot;Server=localhost,1433;Database=test;User Id=$USERNAME;Password=$YOUR_SECURE_PASSWORD;&amp;quot;,
  &amp;quot;MyEventGridTopicUriSetting&amp;quot;: &amp;quot;https://$EVENTGRIDTOPIC.westeurope-1.eventgrid.azure.net/api/events&amp;quot;,
  &amp;quot;MyEventGridTopicKeySetting&amp;quot;: &amp;quot;$EVENTGRIDKEY&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;NB: also add &lt;code&gt;&amp;quot;PYTHON_ISOLATE_WORKER_DEPENDENCIES&amp;quot;: &amp;quot;1&amp;quot;&lt;/code&gt; &lt;a href=&quot;https://github.com/Azure/azure-functions-sql-extension/issues/250&quot;&gt;as per this GitHub issue&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;azure-data-factory&quot;&gt;Azure Data Factory &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#azure-data-factory&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;create-data-factory-pipeline&quot;&gt;Create data factory pipeline &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#create-data-factory-pipeline&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Navigate to the Author tab of your data factory instance and create a new pipeline. Call it &lt;code&gt;EventTriggeredPipeline&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add a pipeline parameter called &lt;code&gt;data&lt;/code&gt; of type &lt;code&gt;string&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add two variables &lt;code&gt;JobStepName&lt;/code&gt; and &lt;code&gt;JobStepMetadata&lt;/code&gt;, both of type &lt;code&gt;string&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;ForEach&lt;/code&gt; activity onto the canvas. Navigate to the settings tab and in the Items box put &lt;code&gt;@array(pipeline().parameters.data)&lt;/code&gt;. &lt;em&gt;What is this doing?&lt;/em&gt; This is setting up the details of what the &lt;code&gt;ForEach&lt;/code&gt; loop will iterate over. In this example, it will be every row that satisifies the conditions of the SQL query that was added to the &lt;code&gt;commandText&lt;/code&gt; in the &lt;code&gt;sql&lt;/code&gt; input binding.
&lt;ul&gt;
&lt;li&gt;
&lt;img class=&quot;img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/custom-event-driven-etl-with-sql-azure-functions-event-grid/2.png&quot; alt=&quot;Screenshot of the data factory interface showing the settings tab. The items box contains the string &#39;@array(pipeline().parameters.data&#39;.&quot; /&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;ForEach&lt;/code&gt; loop, add two &lt;code&gt;Set Variable&lt;/code&gt; activities. For the &lt;code&gt;JobStepName&lt;/code&gt; variable, set the value as &lt;code&gt;@item().JobStepName&lt;/code&gt;, for &lt;code&gt;JobMetadata&lt;/code&gt;, &lt;code&gt;@item().JobMetadata&lt;/code&gt;. Your pipeline is now complete and should look like the below image:
&lt;ul&gt;
&lt;li&gt;
&lt;img class=&quot;img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/custom-event-driven-etl-with-sql-azure-functions-event-grid/3.png&quot; alt=&quot;Screenshot of the data factory interface showing the pipeline that has been built. The ForEach loop activity is in the middle of the screen with the two Set Variable activities inside. The parameters pane is also on display, showing the string parameter called `data`.&quot; /&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;create-custom-event-trigger&quot;&gt;Create Custom Event Trigger &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#create-custom-event-trigger&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You are now going to create a custom event trigger to run the pipeline when events are written onto your Event Grid topic.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;code&gt;Add Trigger&lt;/code&gt; &amp;gt; &lt;code&gt;New/Edit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;At the top of the pane, select the dropdown box and click &lt;code&gt;+ New&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Give the trigger a name e.g. &lt;code&gt;EventTrigger&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Change type to &lt;code&gt;Custom Event&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Use the Account selection method to select your subscription and Event Grid Topic name&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Optional&lt;/em&gt;: Apply Subject filters to your trigger. E.g. Subject begins with &lt;code&gt;adf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Click OK. This will bring up a new pane &lt;code&gt;Trigger Run Parameters&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For the &lt;code&gt;data&lt;/code&gt; parameter, put &lt;code&gt;@triggerBody().event.data&lt;/code&gt; as the value. This passes the Event data from the event on the Event Grid topic into the pipeline at execution time.&lt;/li&gt;
&lt;li&gt;Click Save.&lt;/li&gt;
&lt;li&gt;Once save is complete, click Publish.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;putting-it-all-together&quot;&gt;Putting it all together &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#putting-it-all-together&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You are now ready to run your first event-driven trigger of the data factory pipeline.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start the Azure Function in your local environment (press F5 or click &lt;code&gt;Run&lt;/code&gt; &amp;gt; &lt;code&gt;Start debugging&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Trigger the Function via HTTP by navigating to the Function endpoint in your browser or calling via Postman e.g. &lt;code&gt;http://localhost:7071/api/HttpTrigger1&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;img class=&quot;img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/custom-event-driven-etl-with-sql-azure-functions-event-grid/4.png&quot; alt=&quot;Screenshot of a web browser showing the json response from the Azure Function.&quot; /&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;code&gt;Monitor&lt;/code&gt; tab &amp;gt; &lt;code&gt;Trigger runs&lt;/code&gt; of your data factory and view the successful pipeline execution:
&lt;ul&gt;
&lt;li&gt;
&lt;img class=&quot;img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/custom-event-driven-etl-with-sql-azure-functions-event-grid/5.png&quot; alt=&quot;Screenshot of the data factory interface showing the pipeline having successfully executed. The output of one of the Set Variable activities can be seen, showing the value `{&#39;name&#39;: &#39;JobStepName&#39;, &#39;value&#39;: &#39;joeplumb.com&#39;}`&quot; /&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/custom-event-driven-etl-with-sql-azure-functions-event-grid/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this post, I&#39;ve covered how to get started triggering pipelines in Azure Data Factory using data from MSSQL, with custom event triggers, Event Grid, and Azure Functions. Let me know if this was useful, good luck and happy building!&lt;/p&gt;
&lt;p&gt;- Joe&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Please &lt;a href=&quot;https://bsky.app/profile/joeplumb.com&quot;&gt;message me on Bluesky&lt;/a&gt; if this has been helpful, or useless, or if you have any questions or comments!&lt;/em&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Processing manual data exports with Power Query and Dataverse</title>
		<link href="https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/"/>
		<updated>2022-06-24T00:00:00Z</updated>
		<id>https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/</id>
		<content type="html">&lt;p&gt;I&#39;ve recently been working on project with a requirement to ingest small datasets into Dataverse to serve a Power App. There were (unfortunately) no programmatic export options for this source data - exports were done manually via a reporting interface and gave the user spreadsheet extracts of 1000 records at a time. Additionally, data in the source system could be updated at any time, creating the opportunity for stale data and duplicate records in this downstream system. I needed to build a solution to process these extracts - merging all existing data extracts with the new extracts, and always promoting the latest data in the case of duplicate entries for a given record. This data then needed to be added to the existing Dataverse table, inserting new records and updating existing records &lt;a href=&quot;https://en.wikipedia.org/wiki/Merge_(SQL)&quot;&gt;(more here)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My initial instinct was to implement a solution using code with SQL and Python, but the solution was already entirely oriented around Power Platform (it was a Power App that was being developed!), so, I decided to take on the challenge and try and make use of the existing tools available: enter Power Query and Cloud Flows. (I initially tried to implement the whole solution with PowerQuery, however I couldn&#39;t figure out how to process the merge between the incoming data from the spreadsheets and the existing data in the target table - many thanks to my &lt;a href=&quot;https://www.linkedin.com/in/jonathanballdigital&quot;&gt;esteemed colleague Jonathan Ball&lt;/a&gt; for introducing me to Cloud flows and supporting me with this part of the solution!)&lt;/p&gt;
&lt;p&gt;In this blog, I will walk through the steps to set up this process in your own environment to serve your own data processing use case. The walk-through will be broken into 4 parts; solution overview, ingesting files and data transformation, merging records from your staging table to the production tables, and creating the trigger flow.&lt;/p&gt;
&lt;h2 id=&quot;1.-solution-overview&quot;&gt;1. Solution overview &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/#1.-solution-overview&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&#39;s take a look at the final solution design for what I delivered:&lt;/p&gt;
&lt;img class=&quot;img-fluid-blog rounded d-block &quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/1.png&quot; alt=&quot;&quot; /&gt;
&lt;h3 id=&quot;processing-flow&quot;&gt;Processing flow &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/#processing-flow&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Required data extracted from application manually via built-in data export tool&lt;/li&gt;
&lt;li&gt;Exported file(s) are uploaded to SharePoint folder&lt;/li&gt;
&lt;li&gt;File upload triggers PowerApps Data Flow(s) via Microsoft Flow&lt;/li&gt;
&lt;li&gt;Data Flows process data to clean and conform data staging tables with Power Query. Records are de-duped, taking only the latest records from source for each unique record&lt;/li&gt;
&lt;li&gt;Cloud flows used to compare all inbound records to existing records in Dataverse, inserting new records and updating records that already exist&lt;/li&gt;
&lt;li&gt;Updated data available for consumption in PowerApps&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;2.-ingesting-files-and-staging-the-data&quot;&gt;2. Ingesting files and staging the data &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/#2.-ingesting-files-and-staging-the-data&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;creating-and-populating-the-landing-zone&quot;&gt;Creating and populating the landing zone &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/#creating-and-populating-the-landing-zone&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First thing to do is provision the landing zone for inbound files. I used a SharePoint site in this project because these were already available in the environment and provided an easy, familiar interface for users to interact with.&lt;/p&gt;
&lt;img class=&quot;img-fluid-blog rounded   d-block&quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/2.png&quot; alt=&quot;&quot; /&gt;
&lt;ol&gt;
&lt;li&gt;Create one top-level folder e.g. &lt;code&gt;etlfolder&lt;/code&gt;, with folders underneath this for each inbound dataset. The sub folders can be used to filter files in Power Query to ensure you are processing the right files with each flow (in situations where you are building more than one pipeline).&lt;/li&gt;
&lt;li&gt;Upload some sample data files into this folder. For this example I will be working with these &lt;code&gt;dataexamplex.xlsx&lt;/code&gt; spreadsheets which you can &lt;a href=&quot;https://github.com/joe-plumb/powerqueryupsert&quot;&gt;download from GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;staging-the-data-with-power-query&quot;&gt;Staging the data with Power Query &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/#staging-the-data-with-power-query&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The next task is to create a new dataflow with Power Query to ingest and land the data from your files into the staging table:&lt;/p&gt;
&lt;h4 id=&quot;create-the-dataflow&quot;&gt;Create the Dataflow &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/#create-the-dataflow&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;In Power Apps, go to Dataverse &amp;gt; Dataflows and click &lt;code&gt;New Dataflow +&lt;/code&gt;. Give the dataflow a name and click &lt;code&gt;Create&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select the data source - for this example, we will be reading the &lt;code&gt;.xlsx&lt;/code&gt; files we have already uploaded to SharePoint, so select &lt;code&gt;SharePoint folder&lt;/code&gt;. Enter your SharePoint site URL and configure the connection credentials. Click &lt;code&gt;Next&lt;/code&gt;.&lt;br /&gt;
&lt;img class=&quot;img-fluid-blog rounded   d-block&quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/5.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;A preview of the files you have uploaded into the folder will appear. Filter the file list if necessary, click &lt;code&gt;Combine&lt;/code&gt;, and wait for the Combine files dialog to appear. Select the sheet with your data in (e.g. &lt;code&gt;Sheet 1&lt;/code&gt;), and click &lt;code&gt;Transform Data&lt;/code&gt;. Power Query will create some helper queries to unpack the data from all the &lt;code&gt;xlsx&lt;/code&gt; files you have uploaded to SharePoint. If you experience errors here, or need to adjust the logic at all because of different source file types etc, you can find &lt;a href=&quot;https://docs.microsoft.com/power-query/combine-files-overview&quot;&gt;more details on the Combine Files approach in the docs&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should now see the Power Query window with some helper functions and queries created for you, and all the rows of data from your files. Next, we will process and clean the data to only get the latest rows from your files.&lt;/p&gt;
&lt;h4 id=&quot;cleaning-data-and-filtering-duplicates&quot;&gt;Cleaning data and filtering duplicates &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/#cleaning-data-and-filtering-duplicates&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;First, perform any additional data cleaning activities you require. Ensure data types are set correctly, remove rows with missing values, etc.&lt;/li&gt;
&lt;li&gt;Order the data by your &lt;code&gt;LastModifiedDate&lt;/code&gt; column in descending order by clicking the arrow next to the column header and clicking &lt;code&gt;Sort descending&lt;/code&gt;.&lt;br /&gt;
&lt;img class=&quot;img-fluid-blog rounded   d-block&quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/6.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;Next, we will use the &lt;code&gt;Table.Buffer&lt;/code&gt; function to force Power Query to load all the data into memory and ensure you always get the latest row when you filter out the distinct values - credit for this detail goes to &lt;a href=&quot;https://exceleratorbi.com.au/remove-duplicates-keep-last-record-power-query/&quot;&gt;Matt Allington of ExceleratorBI&lt;/a&gt;. Open the advanced editor for the query and wrap the last row with the &lt;code&gt;Table.Buffer&lt;/code&gt; function e.g.&lt;pre&gt;&lt;code&gt;  #&amp;quot;Sorted rows&amp;quot; = Table.Sort(#&amp;quot;Changed column type&amp;quot;, lastmodified)
&lt;/code&gt;&lt;/pre&gt;
becomes&lt;pre&gt;&lt;code&gt;  #&amp;quot;Sorted rows&amp;quot; = Table.Buffer(Table.Sort(#&amp;quot;Changed column type&amp;quot;, lastmodified))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Finally, remove the duplicates on your ID column by right clicking the column header and clicking &lt;code&gt;Remove duplicates&lt;/code&gt;&lt;br /&gt;
&lt;img class=&quot;img-fluid-blog rounded   d-block&quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/7.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Next&lt;/code&gt;. In the map table dialogue, ensure the query you just created is set to load to a new table, and that the Unique primary name column is set to your ID column. Give the table a unique name. Check the &lt;code&gt;Delete rows that no longer exist in the query output&lt;/code&gt; option. Click &lt;code&gt;Next&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Leave the refresh as &lt;code&gt;Refresh manually&lt;/code&gt;, and click &lt;code&gt;Publish&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once the dataflow has been published it will run for the first time automatically - verify that you see the records in your staging table.&lt;/p&gt;
&lt;h2 id=&quot;3.-merge-from-staging-to-production-table&quot;&gt;3. Merge from Staging to Production table &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/#3.-merge-from-staging-to-production-table&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Create new Cloud flow by navigating to &lt;code&gt;Flows&lt;/code&gt; &amp;gt; &lt;code&gt;New flow&lt;/code&gt; &amp;gt; &lt;code&gt;Automated cloud flow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Give the flow a name, select &lt;code&gt;When a dataflow refresh completes&lt;/code&gt; as the trigger and click &lt;code&gt;Create&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Populate the trigger with the details of the Power Query flow you created in the previous step.&lt;/li&gt;
&lt;li&gt;Build the rest of the flow with the following logic
&lt;ol&gt;
&lt;li&gt;List rows in the staging table with the &lt;code&gt;List rows&lt;/code&gt; activity for Dataverse&lt;br /&gt;
&lt;img class=&quot;img-fluid-blog rounded   d-block&quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/8.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;Add a new step to list all rows from your production/target table with the same &lt;code&gt;List rows&lt;/code&gt; activity for Dataverse. Then go to the advanced settings and in the Filter rows section, add a condition to compare the ID column name for the target table to the ID value from the first List Rows activity e.g. Type &lt;code&gt;ContactId eq&lt;/code&gt; then click the Id value in the dynamic content window. Selecting the unique identifier from the Dynamic content section will embed this activity in an &lt;code&gt;Apply to each&lt;/code&gt; loop.&lt;br /&gt;
&lt;img class=&quot;img-fluid-blog rounded   d-block&quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/9.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;Still inside the &lt;code&gt;Apply to each&lt;/code&gt; loop, add a condition activity with one condition. This condition will establish whether the record from the staging table exists in the target table. The condition should use expressions. The left hand side should be &lt;code&gt;empty(outputs(&#39;List_Contacts&#39;)?[&#39;body/value&#39;])&lt;/code&gt;, where &lt;code&gt;List_Contacts&lt;/code&gt; is your second list activity. Select &lt;code&gt;is equal to&lt;/code&gt; for the condition. The right hand side should be &lt;code&gt;true&lt;/code&gt; (make sure you add this as an expression).&lt;br /&gt;
&lt;img class=&quot;img-fluid-blog rounded   d-block&quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/10.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;Finally, add the details to process the inbound records; in the &lt;code&gt;If yes&lt;/code&gt; for the condition, add a &lt;code&gt;Add new row&lt;/code&gt; activity and select the target table. Map the columns in the target table to the columns from the dynamic content pane. In the &lt;code&gt;If no&lt;/code&gt; section, add an &lt;code&gt;Update row&lt;/code&gt; activity, referencing the same target table, and adding the same mapping between the columns in the target table, and the columns in the dynamic content pane for the row from the first &lt;code&gt;List rows&lt;/code&gt; activity. This will automatically add another &lt;code&gt;Apply to each&lt;/code&gt; loop again - leave this as it is. &lt;strong&gt;NB: be sure that the ID column in your inbound data is mapped to the ID column on the target table&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Once you have completed the above actions, you should have a flow that looks like the below&lt;br /&gt;
&lt;img class=&quot;img-fluid-blog rounded   d-block&quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/11.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Save&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;4.-creating-the-trigger&quot;&gt;4. Creating the trigger &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/#4.-creating-the-trigger&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Finally, Because we want this process to execute every time we upload more data, we need to create a new Automated Cloud Flow that uses the SharePoint &amp;quot;When a file is created in a folder&amp;quot; trigger. This will trigger the dataflow to run, which will in turn trigger the Cloud flow to run to update the Dataverse table.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to flows in your environment and select `New Flow &amp;gt; Automated cloud flow&amp;quot;:&lt;br /&gt;
&lt;img class=&quot;img-fluid-blog rounded   d-block&quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/3.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;Give your flow a name, and select the SharePoint &amp;quot;When a file is created in a folder&amp;quot; trigger. When the canvas loads, update the metadata in your flow to reference the new landing zone folder you have just created for the inbound data files. Then, add a &lt;code&gt;Refresh a dataflow&lt;/code&gt; step below the trigger and add the references for the cloud flow you created in the previous step. You should now have a flow that looks like this:&lt;br /&gt;
&lt;img class=&quot;img-fluid-blog rounded   d-block&quot; src=&quot;https://joeplumb.com/img/processing-manual-data-exports-with-powerquery-and-dataverse/4.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;The pipeline is complete! Save your flow and test the end to end process by uploading a new spreadsheet to the SharePoint site with some additional rows of data to check everything is configured correctly.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/processing-manual-data-exports-with-powerquery-and-dataverse/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this post, I&#39;ve covered how to build and automated pipeline to merge records from multiple point-in-time file extracts into Dataverse using Dataflows and Cloud flows in Microsoft Power Platform. Let me know if this was useful, good luck and happy building!&lt;/p&gt;
&lt;p&gt;- Joe&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Please &lt;a href=&quot;https://bsky.app/profile/joeplumb.com&quot;&gt;message me on Bluesky&lt;/a&gt; if this has been helpful, or useless, or if you have any questions or comments!&lt;/em&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Calling Microsoft Graph APIs via Logic Apps with service principals and app permissions</title>
		<link href="https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/"/>
		<updated>2021-12-06T00:00:00Z</updated>
		<id>https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/</id>
		<content type="html">&lt;p&gt;I was doing a piece of work today where I needed to run some API calls against Microsoft Graph APIs to collect and process some information about service accounts (known as &lt;a href=&quot;https://devblogs.microsoft.com/devops/demystifying-service-principals-managed-identities/#service-principal&quot;&gt;service principals&lt;/a&gt; in Azure AD parlance). Although there are lots of built in connectors for Logic Apps, there aren&#39;t any out of the box connectors for the Microsoft Graph APIs, so to build this integration we currently need to rely on the HTTP connector.&lt;/p&gt;
&lt;p&gt;Thankfully, my colleague Tareq Saifi has written &lt;a href=&quot;https://techcommunity.microsoft.com/t5/integrations-on-azure-blog/calling-graph-api-from-azure-logic-apps-using-delegated/ba-p/1997666&quot;&gt;this article on the Microsoft Tech Community&lt;/a&gt; that explains how to call the Graph APIs using delegated permissions. This was enough to get me started however I hit some issues along the way that I wanted to document, as a note for future me and maybe useful to others too.&lt;/p&gt;
&lt;p&gt;The main differences and issues I faced compared to Tareq&#39;s blog were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;needed to &lt;a href=&quot;https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/#delegated-permissions-versus-application-permissions&quot;&gt;integrate via application permissions&lt;/a&gt;, as this was going to be a back end process to evaluate service principal registrations.&lt;/li&gt;
&lt;li&gt;issues finding &lt;a href=&quot;https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/#which-permissions-to-grant?&quot;&gt;which permissions I needed to add to my service principal&lt;/a&gt; to perform the actions to solve my problem.&lt;/li&gt;
&lt;li&gt;issues &lt;a href=&quot;https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/#permission-to-grant-permission&quot;&gt;granting the permissions to the service principal&lt;/a&gt; to call the Graph APIs.&lt;/li&gt;
&lt;li&gt;response from the &lt;a href=&quot;https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/#a-small-schema-change&quot;&gt;&lt;code&gt;oauth2/token&lt;/code&gt; API had a different schema&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;ll walk through each of these in turn through the remainder of this post.&lt;/p&gt;
&lt;h2 id=&quot;delegated-permissions-versus-application-permissions&quot;&gt;Delegated permissions versus Application permissions &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/#delegated-permissions-versus-application-permissions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first thing that was different between what I was attempting to achieve and Tareq&#39;s blog is the subtle but important distinction when selecting the permission type when granting appropriate permissions to your service principal (step 1M in the original post) - you can select either &lt;a href=&quot;https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#permission-types&quot;&gt;delegated or application permissions&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Delegated permissions&lt;/strong&gt; are used by apps that &lt;em&gt;have&lt;/em&gt; a signed-in user present. For these apps, either the user or an administrator consents to the permissions that the app requests. The app is delegated with the permission to act as a signed-in user when it makes calls to the target resource.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Application permissions&lt;/strong&gt; are used by apps that run &lt;em&gt;without&lt;/em&gt; a signed-in user present, for example, apps that run as background services or daemons. Only an administrator can consent to application permissions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Due to the nature of the service I was implementing, application permissions were the appropriate selection. This is then where the next issue came up - what permissions do I actually need to grant here?&lt;/p&gt;
&lt;h2 id=&quot;which-permissions-to-grant%3F&quot;&gt;Which permissions to grant? &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/#which-permissions-to-grant%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After I had selected application permissions, it wasn&#39;t immediately clear to me where find which actions would be authorized in the Graph API when when choosing which permissions to grant (step 1N from original post).&lt;/p&gt;
&lt;img class=&quot; img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/using-logic-apps-with-microsoft-graph-api/1.png&quot; alt=&quot;Screenshot of the &#39;Request API permissions&#39; blade in Azure Active Directory. Microsoft Graph API anbd Application permissions are selected in the blade. A list of potential permissions are listed below. The list reads: AccessReview, AdministrativeUnit, AgreementAcceptance, Agreement.&quot; /&gt;
&lt;p&gt;The answer lay in the &lt;a href=&quot;https://docs.microsoft.com/graph/api/serviceprincipal-list?view=graph-rest-1.0&amp;amp;tabs=http&quot;&gt;Graph REST API specification&lt;/a&gt;. In this example you can see clearly which permissions allow you to call each API, in order of least to most privileged. The next issue was actually a blocker to progress this much further - I had insufficient permission to grant permissions!&lt;/p&gt;
&lt;h2 id=&quot;permission-to-grant-permission&quot;&gt;Permission to grant permission &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/#permission-to-grant-permission&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The final hurdle was in granting the permissions to my service principal. Tareq covers this in his blog in step 1O - however this glosses over the fact that &lt;em&gt;not every user has permission to change permissions in your Azure AD tenant&lt;/em&gt;. Checking &lt;a href=&quot;https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app#prerequisites&quot;&gt;the docs...&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Azure account must have permission to manage applications in Azure Active Directory (Azure AD). Any of the following Azure AD roles include the required permissions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Application administrator&lt;/li&gt;
&lt;li&gt;Application developer&lt;/li&gt;
&lt;li&gt;Cloud application administrator&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unsurprisingly I am not afforded any of these roles in the internal Microsoft AD tenant - so to build this demo I was at a dead end. I worked around this by using a different Azure AD tenant (where I host this site, in fact).&lt;/p&gt;
&lt;img class=&quot; img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/using-logic-apps-with-microsoft-graph-api/2.png&quot; alt=&quot;Screenshot of the &#39;examplegraphapi&#39; service principal in the joeplumb Azure Active Directoy tenant. The screenshot shows the configured permissions for the service principal, which includes &#39;Application.Read.All&#39; and &#39;User.Read&#39;.&quot; /&gt;
&lt;p&gt;If I needed to work through this challenge in a tenant where this was a blocker, the solution would be to talk to the administrator of the tenant and agree to sign off on permissions for my App Registration to have the required permissions to perform the operations I needed. This block is here for a reason - a good permissions management strategy is all part of sound security posture.&lt;/p&gt;
&lt;h2 id=&quot;a-small-schema-change&quot;&gt;A small schema change &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/#a-small-schema-change&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The final thing that appears to have changed since the initial post was that the schema for the response from the &lt;code&gt;oauth2/token&lt;/code&gt; API was different - for me, it took the following structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &amp;quot;properties&amp;quot;: {
        &amp;quot;access_token&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
        },
        &amp;quot;expires_in&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;
        },
        &amp;quot;ext_expires_in&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;
        },
        &amp;quot;token_type&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
        }
    },
    &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was easy to manage with the &lt;em&gt;Use sample payload to generate schema&lt;/em&gt; functionality in the &lt;em&gt;Parse json&lt;/em&gt; activity.&lt;/p&gt;
&lt;img class=&quot; img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/using-logic-apps-with-microsoft-graph-api/3.png&quot; alt=&quot;Screenshot of the &#39;Parse json&#39; activity in the Logic app designer. The task is taking the body of the response from the HTTP call as an input, and parsing the json response. There is an orange arrow pointing to the &#39;Use sample payload to generate schema&#39; button.&quot; /&gt;
&lt;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/azure/active-directory/develop/authorization-basics&quot;&gt;Authorization basics - Microsoft identity platform | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/azure/active-directory/develop/authentication-vs-authorization&quot;&gt;Authentication vs. authorization - Microsoft identity platform | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.microsoft.com/graph/graph-explorer&quot;&gt;Graph Explorer | Microsoft Graph&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/graph/use-postman&quot;&gt;Use Postman with the Microsoft Graph API | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/using-logic-apps-with-microsoft-graph-api/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ve walked through some of the subtle differences in permission configuration to enable your service principals to interact with the Graph API. I&#39;ve also highlighted some of the challenges I faced along the way to help accelerate others.&lt;/p&gt;
&lt;p&gt;Please &lt;a href=&quot;https://bsky.app/profile/joeplumb.com&quot;&gt;message me on Bluesky&lt;/a&gt; if this has been helpful, or useless, or if you have any questions or comments!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>SQL Server Data Tools - A deeper look</title>
		<link href="https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/"/>
		<updated>2021-04-27T00:00:00Z</updated>
		<id>https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/</id>
		<content type="html">&lt;p&gt;In my last blog, I introduced the importance of leveraging DevOps practices and principles for your infrastructure and analytics artefacts, as well as discussing two possible approaches for managing changes to a database as part of this process, and some tooling that you can use to bring this theory into practice.&lt;/p&gt;
&lt;p&gt;In this blog, I will dive deeper into the database project itself – what it is, how to manage it, and give a little more context so you know what to expect as you get started yourself.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;All screenshots have been captured from Visual Studio 2019 Community Edition, which is currently my preferred tool for working with SSDT, however the same tools are now available as part of Azure Data Studio!&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;database-projects&quot;&gt;Database projects &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/#database-projects&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Database projects are composed of the &lt;code&gt;.sql&lt;/code&gt; files that make up the data model in your system, and several configuration files that SQL Server Data Tools (SSDT) uses to manage, control, and apply changes to your database. When starting a new project you have two options: you can create a new empty database project, or import a database schema from an existing database, .sql script file or a Data-tier application (.dacpac).&lt;/p&gt;
&lt;p&gt;As the focus of this blog is on SSDT project files, I have created a new “SQL Server Database Project” by following the “New Project” wizard in Visual Studio.&lt;/p&gt;
&lt;img class=&quot;img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/sql-server-data-tools-a-deeper-look/1.png&quot; alt=&quot;Screenshot of the &#39;Create a new project&#39; button in visual studio. Text below says &#39;Choose a project template with code scaffolding to get started&#39;. There is a small icon on the left of the button which looks like 2 pieces of clean paper.&quot; /&gt;
&lt;p&gt;After instantiating a blank project, your solution will look like this:&lt;/p&gt;
&lt;img class=&quot;img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/sql-server-data-tools-a-deeper-look/2.png&quot; alt=&quot;A screenshot of the solution explorer in visual studio. There are navigation buttons across the top to view the files in different ways. The empty database solution called &#39;Database1&#39; is present, with &#39;Properties&#39; and &#39;References&#39; as child options of the project.&quot; /&gt;
&lt;p&gt;If you switch to the Folder view or look at this new directory in Windows Explorer, you’ll see these files:&lt;/p&gt;
&lt;img class=&quot;img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/sql-server-data-tools-a-deeper-look/3.png&quot; alt=&quot;Similar to the previous screenshot, this now shows the folder view of the newly created database project. It displays the underlying files that make up the solution. The files are: Database1.sln, Database1.sqlproj, Database1.sqlproj.user, Database1.sqlprojAssemblyReference.cache. These files are discussed in more detail in the next part of the blog.&quot; /&gt;
&lt;p&gt;Let’s dig into each of these files – what are they for and how should you manage them? Working from the bottom up:&lt;/p&gt;
&lt;h3 id=&quot;.sln&quot;&gt;&lt;code&gt;.sln&lt;/code&gt; &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/#.sln&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;.sln&lt;/code&gt; file maintains the state information for the project. It is text based (you can open it in a text editor of your choice and take a look), and contains data that the environment and IDE uses to find a load parameters and packages that are required to work with items in the project. It is designed to be shared with all developers who interact with the repository. For an in depth look at &lt;code&gt;.sln&lt;/code&gt; files, &lt;a href=&quot;https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019&quot;&gt;check the documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;✅ Store &lt;code&gt;.sln&lt;/code&gt; in version control&lt;/p&gt;
&lt;h3 id=&quot;.sqlproj-and-.sqlproj.user&quot;&gt;&lt;code&gt;.sqlproj&lt;/code&gt; and &lt;code&gt;.sqlproj.user&lt;/code&gt; &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/#.sqlproj-and-.sqlproj.user&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The proj files contain the properties for your project and is used by Visual Studio’s “Build” action to tell it what to build. The main difference is the &lt;code&gt;.sqlproj&lt;/code&gt; contains properties that are specific to the project (e.g. which &lt;code&gt;.sql&lt;/code&gt; files in the project should be included when creating the data model), and the &lt;code&gt;.sqlproj.user&lt;/code&gt; file contains &lt;em&gt;your specific user options&lt;/em&gt; for the project. As you work with your project to add and change database objects, you will see this file get updated. For more info, see &lt;a href=&quot;https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2008/ms171339(v=vs.90)?redirectedfrom=MSDN#file-handling-by-source-control&quot;&gt;this page in the documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;✅ Store &lt;code&gt;.sqlproj&lt;/code&gt; in version control&lt;br /&gt;
❌ Do not store &lt;code&gt;.sqlproj.user&lt;/code&gt; in version control&lt;/p&gt;
&lt;h3 id=&quot;sqlprojassemblyreference.cache&quot;&gt;&lt;code&gt;sqlprojAssemblyReference.cache&lt;/code&gt; &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/#sqlprojassemblyreference.cache&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.cache&lt;/code&gt; files in a Visual Studio project are used to track the work that previous build processes have done to speed up subsequent builds in the local environment. Because these are specific to the local environment, the &lt;code&gt;.cache&lt;/code&gt; files should not be checked in to version control. This is also reflected in the &lt;a href=&quot;https://github.com/github/gitignore/blob/master/VisualStudio.gitignore#L225-L229&quot;&gt;boilerplate &lt;code&gt;.gitignore&lt;/code&gt; file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;❌ Do not store &lt;code&gt;.cache&lt;/code&gt; in version control&lt;/p&gt;
&lt;h2 id=&quot;working-with-ssdt-%E2%80%93-development-best-practice&quot;&gt;Working with SSDT – development best practice &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/#working-with-ssdt-%E2%80%93-development-best-practice&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;set-the-target-platform&quot;&gt;Set the target platform &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/#set-the-target-platform&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Make sure this is set correctly –Visual Studio uses this to automatically detecting any error in your code and to build a compatible DACPAC for the target data store. Go into the project settings by double clicking on “Properties”, to make sure the correct target platform is set.&lt;/p&gt;
&lt;img class=&quot;img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/sql-server-data-tools-a-deeper-look/4.png&quot; alt=&quot;A screenshot of the project settings for the solution. The dropdown menu for &#39;Target Platform&#39; has been selected, and all supported MSSQL database sources are presented as options. These are: SQL Server 2005, 2008, 2012, 2014, 2016, 2017, 2019 or Azure SQL Database Managed Instance, Microsoft Azure SQL Database, and Microsoft Azure SQL Data Warehouse.&quot; /&gt;
&lt;h3 id=&quot;organize-your-files&quot;&gt;Organize your files &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/#organize-your-files&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is not a requirement, but I find it easier to navigate and develop when I have the same folder structure I am familiar with from Management Studio. If you import a data model from an existing project or SQL source, you can choose what structure Visual Studio should use – I prefer the Schema/Object option.&lt;/p&gt;
&lt;img class=&quot;img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/sql-server-data-tools-a-deeper-look/5.png&quot; alt=&quot;A screenshot of the import settings when importing and existing data model into a new database project. The dropdown menu for &#39;folder structure&#39; has been selected. The options are: None, Schema, Object type, and Schema&#92;Object type. The last one is my favourite.&quot; /&gt;
&lt;p&gt;When creating from scratch, you’ll have to build this into the project yourself. I aim for something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Schema name (e.g. dbo)
|- Tables
|- Views
|- Programmability
  | Stored procedures
|- Security
  | Users
  | Roles
  | Security Policies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have seen others have separate folders for &lt;code&gt;fact&lt;/code&gt; and &lt;code&gt;dimension&lt;/code&gt; tables, too.&lt;/p&gt;
&lt;h3 id=&quot;use-the-designer-to-create-or-edit-objects&quot;&gt;Use the designer to create or edit objects &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/#use-the-designer-to-create-or-edit-objects&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I’m a big fan of the built-in designer as it saves time searching through docs for valid syntax. To create a new object, right-click on the solution explorer and click Add &amp;gt; New Item. This will bring up a prompt where you can select the object types you wish to add, with some boilerplate code to get you started.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/sql-server-data-tools-a-deeper-look/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’ve talked through the files you can expect to run into when working with SQL Server Data Tools projects, made some recommendations on how to manage these files in your version control repository, and given some best practice for structuring the project and using Visual Studio features to speed up your development.&lt;/p&gt;
&lt;p&gt;Please &lt;a href=&quot;https://bsky.app/profile/joeplumb.com&quot;&gt;message me on Bluesky&lt;/a&gt; if this has been helpful, or useless, or if you have any questions or comments!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Collaborative Data Systems on Azure</title>
		<link href="https://joeplumb.com/blog/collaborative-data-systems-on-azure/"/>
		<updated>2021-04-17T00:00:00Z</updated>
		<id>https://joeplumb.com/blog/collaborative-data-systems-on-azure/</id>
		<content type="html">&lt;p&gt;Use of DevOps practices and principles for solution design, implementation, and operations of infrastructure to support business has been increasing in popularity over the years. &lt;a href=&quot;https://agilemanifesto.org/&quot;&gt;Agile development&lt;/a&gt; enables teams to move fast and focus on value; we are empowered to chart our own path to deliver functionality that delights users, and adapt to changing requirements over time. &lt;a href=&quot;https://www.hashicorp.com/resources/everything-as-code-the-future-of-ops-tools&quot;&gt;Everything-as-code is standard&lt;/a&gt;, and being able to rapidly provision additional capacity or develop new features to meet demand is taken as a given.&lt;/p&gt;
&lt;p&gt;These techniques and expections from software development are now front and centre in the modern data ecosystem. From small scale analytics projects to &lt;a href=&quot;https://martinfowler.com/articles/data-monolith-to-mesh.html&quot;&gt;enterprise data mesh architecture&lt;/a&gt;, these systems will almost always benefit from modern tooling and agile development practices. Effective analytics teams need reliable mechanisms to manage and promote changes across environments, collaborate with others, and ensure reliability of these changes before they get to production. Further, these mechanisms &lt;em&gt;must account for the entire analytics lifecycle&lt;/em&gt; - the infrastructure deployment and management (i.e. compute, storage, networking), as well as the &lt;strong&gt;analytics artefacts themselves&lt;/strong&gt; (i.e. data pipelines, dashboards, models) that are developed on top of the platform.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A data system without processes to manage the data artefacts within are likely to cost more to run, have reduced impact, and higher levels of technical debt.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this post, I will look at what makes data systems different when it comes to agile development, review two patterns for version control with databases and discuss the benefits and considerations of both approaches, and highlight how other components of the data platform can also integrate with an overall delivery pipeline to manage data artefacts.&lt;/p&gt;
&lt;h2 id=&quot;what-makes-devops-with-data-different%3F&quot;&gt;What makes DevOps with Data different? &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/collaborative-data-systems-on-azure/#what-makes-devops-with-data-different%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There is one subtle difference in a data system - that of &lt;strong&gt;state management&lt;/strong&gt;. Application development does not typically have this problem as new versions or features simply supersede the old. You don&#39;t want to lose data when deploying changes, so ensuring this process is managed reliably is critical. In order to ensure the data remains in place and usable, the processes that promote changes to your data infrastucture need to take the existing state of the system into account. There are common two approches to managing this process: &lt;em&gt;migration-based&lt;/em&gt; deployments and &lt;em&gt;state-based&lt;/em&gt; deployments.&lt;/p&gt;
&lt;h3 id=&quot;migration-based-deployment&quot;&gt;Migration-based deployment &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/collaborative-data-systems-on-azure/#migration-based-deployment&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At a high level, migration-based deployments capture a “starting point” implementation of a database script, which represents the initial state. Changes and additions are then developed from this point as incremental migration scripts, which are checked into version control and applied to the database. The state of the database can be rebuilt by re-running the database initialization script, then all subsequent migration scripts in sequence on a new instance.&lt;/p&gt;
&lt;img class=&quot;img-fluid&quot; src=&quot;https://joeplumb.com/img/collaborative-data-systems/1.png&quot; alt=&quot;A diagram depicting the migration based approach to database development. A series of boxes across the top of the image represents the state of the database in each stage. Migration scripts are depicted below, which shows how scripts are applied in sequence to get from the &#39;base state&#39; database to the latest version, through the application of the scripts.&quot; /&gt;
&lt;p&gt;&lt;em&gt;Migration-based deployment: changes are managed and deployed to the live database by applying a series of &amp;quot;migration scripts&amp;quot; on the system&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;state-based-deployment&quot;&gt;State-based deployment &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/collaborative-data-systems-on-azure/#state-based-deployment&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A state-based deployment approach instead stores all database objects in source control as &lt;code&gt;.sql&lt;/code&gt; files. New objects and updates to existing objects and are developed and added to these files in the repository, and tools are used to compare updated models to production databases to generate scripts to update the model in line with these changes. The scripts that are generated are similar to those that a developer would author in the migration-based deployment approach.&lt;/p&gt;
&lt;img class=&quot;img-fluid&quot; src=&quot;https://joeplumb.com/img/collaborative-data-systems/2.png&quot; alt=&quot;A diagram depicting the state based approach to database development. The image shows a database project on the left, and the live database on the right. A box depicting &#39;schema compare&#39; functionality running between the project and the live database is in the middle, with a difference script in the bottom right corner being generated as a result of this operation. The generated script is then executed on the live database to bring the system up to date.&quot; /&gt;
&lt;p&gt;&lt;em&gt;State-based deployment: Tooling is used to compare as-is and to-be state of the database - tooling like SQL Server Data Tools then generates a script to update the target database to the same state as the project model&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;which-is-right-for-me%3F&quot;&gt;Which is right for me? &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/collaborative-data-systems-on-azure/#which-is-right-for-me%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There is a case to be made for both approaches, and you need to consider the level of control you require over the changes you are making to your database.&lt;/p&gt;
&lt;p&gt;I have a preference for state-based deployments as this approach relies on tools to generate the update scripts, leaving developers to focus on the changes that need to be made and hand off responsibility for the underlying state changes to the tooling. The state based deployment also reduces the barrier to entry as the repository itself is the control point for the state of the system, so updating objects in the database is similar to adding new functionality in a software project, which developers are likely to be more familiar with.&lt;/p&gt;
&lt;p&gt;Migration based deployment does devolve full control over the changes that are made to developers, which may be preferable in some circumstances.&lt;/p&gt;
&lt;p&gt;You might consider a hybrid approach too - state-based deployment tooling has the concept of pre and post deployment script steps, and these can be used to make manual changes to a deployment after the generated scripts have been created and executed in the database.&lt;/p&gt;
&lt;p&gt;For further thoughts on the trade-offs between the two approaches, see &lt;a href=&quot;https://samirbehara.com/2018/04/16/database-delivery-state-based-vs-migration-based/&quot;&gt;this blog post from Samir Behara&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-can-i-start-applying-these-processes-to-my-data-system-in-azure%3F&quot;&gt;How can I start applying these processes to my Data System in Azure? &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/collaborative-data-systems-on-azure/#how-can-i-start-applying-these-processes-to-my-data-system-in-azure%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you opt for a state based approach to development, Azure SQL DB and Azure Synapse both have support for SQL Server Data Tools (SSDT). SSDT is free, and available as part of &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/ssdt/download-sql-server-data-tools-ssdt?view=sql-server-ver15#ssdt-for-visual-studio-2019&quot;&gt;Visual Studio Community 2019&lt;/a&gt;. As part of the management of the analytics artefacts in your data system, you will also need to consider how you manage changes to data pipelines and dashboards. The support for this in Azure Data Factory is very comprehensive - the most recent update to this in February of this year has added support for the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/data-factory/continuous-integration-deployment-improvements&quot;&gt;validation and publish of artefacts as part of your CI pipeline&lt;/a&gt;, meaning you can then manage the remaining deployment process in your pipelines. Automated deployment of artefacts in PowerBI takes a slightly different approach and use of version control is currently &lt;a href=&quot;https://docs.microsoft.com/en-us/power-bi/create-reports/deployment-pipelines-best-practices#version-control-for-pbix-files&quot;&gt;limited to use with OneDrive&lt;/a&gt; (i.e. not git) - you can &lt;a href=&quot;https://docs.microsoft.com/en-us/power-bi/create-reports/deployment-pipelines-overview&quot;&gt;read about deployment pipelines here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;NB: The &amp;quot;Database Projects&amp;quot; functionality in SSDT is also &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/azure-data-studio/extensions/sql-database-project-extension?view=sql-server-ver15&quot;&gt;available in Azure Data Studio&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;further-reading-on-this-topic&quot;&gt;Further reading on this topic &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/collaborative-data-systems-on-azure/#further-reading-on-this-topic&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://martinfowler.com/articles/evodb.html&quot;&gt;Evolutionary Database Design | martinfowler.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.sqlchick.com/entries/2016/1/10/why-you-should-use-a-ssdt-database-project-for-your-data-warehouse&quot;&gt;Why You Should Use a SSDT Database Project For Your Data Warehouse | sqlchick.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jamesserra.com/archive/2020/01/devops-for-databases-dataops/&quot;&gt;DevOps for databases: “DataOps” | jamesserra.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/collaborative-data-systems-on-azure/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We have looked at different approaches you can consider when adopting DevOps processes in your data system. I have focused on approaches to manage data artefacts in the database, and shared some further content and patterns to extend this process to manage lifecycle of the analytics artefact including the data pipelines and dashboard/reports.&lt;/p&gt;
&lt;p&gt;Over the next few weeks I am going to write up some further information on this pattern, covering how you can get started with SQL Server Data Tools and Azure Synapse, and a deeper look at the SSDT Database Project. If you&#39;re looking for a hands on example, my &lt;a href=&quot;https://github.com/joe-plumb/mdw-azure-terraform/&quot;&gt;Modern Data Warehouse with Terraform example&lt;/a&gt; I created earlier in the year to demonstrate how these systems can be managed and maintained should serve as a good starting point!&lt;/p&gt;
&lt;p&gt;Please &lt;a href=&quot;https://bsky.app/profile/joeplumb.com&quot;&gt;message me on Bluesky&lt;/a&gt; if this has been helpful, or useless, or if you have any questions or comments!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Fourth post - migration to 11ty.</title>
		<link href="https://joeplumb.com/blog/fourth-post/"/>
		<updated>2021-01-06T00:00:00Z</updated>
		<id>https://joeplumb.com/blog/fourth-post/</id>
		<content type="html">&lt;p&gt;Well, there we are folks. Boris Johnson announces another national lockdown and it seems the best response I can come up with is ... refactoring my blog to 11ty!&lt;/p&gt;
&lt;p&gt;It&#39;s been about 6 months since I updated my Angular-based site with blogging capabilities, which was a great learning experience, as well as an incredibly basic and fragile (very 2020) first attempt, lacking some notable features which I really wanted like tags, RSS, and section deeplinks. Enter, Eleventy (11ty).&lt;/p&gt;
&lt;p&gt;In my initial search for frameworks I could use last year, I did review 11ty - in fact, the main feature I was after was the ability to write in markdown and have everything else handled for me, which 11ty does very well. I was instead attracted by the simplicity of a blog that I read, and the fact that I thought I would want some server-side compute element, and so stumbled down the rabbit hole of angular-ness, which was great fun but ultimately a classic case of over-engineering. At least I didn&#39;t deploy it on Kubernetes.&lt;/p&gt;
&lt;p&gt;So, here we are. Visually similar, perhaps a little faster (thanks to this being pre-generated), and heaps more functionality, for a few hours work. The power of open source!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Building analytical data systems with Azure Synapse: A rough guide</title>
		<link href="https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/"/>
		<updated>2020-11-26T00:00:00Z</updated>
		<id>https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/</id>
		<content type="html">&lt;p&gt;Over the last few months I&#39;ve had the opportunity to spend more time working on enterprise analytics and business intelligence scenarios using Azure, based on the widely published and adopted &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/solution-ideas/articles/modern-data-warehouse&quot;&gt;Modern Data Warehouse architecture&lt;/a&gt;. It&#39;s been incredibly satisfying to refresh my visualisation and data modelling skills with PowerBI, and go deep on data orchestration and warehousing using Azure Synapse. As both a note to myself and a helping hand for others on the same journey,	 I wanted to reflect on what I&#39;ve learned and share some key take aways after some hands on training and delivery of solutions for customers.&lt;/p&gt;
&lt;p&gt;This blog will assume some familiarity with data processing patterns, tools, and techniques, and be broadly broken out into two sections; the &lt;em&gt;process and approach&lt;/em&gt; to increase your likelihood of success, and &lt;em&gt;technical considerations and decisions&lt;/em&gt; you&#39;ll encounter as you build out your MVP.&lt;/p&gt;
&lt;h2 id=&quot;process-and-approach&quot;&gt;Process and approach &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#process-and-approach&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h4 id=&quot;allocate-time-to-learn-the-data&quot;&gt;Allocate time to learn the data &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#allocate-time-to-learn-the-data&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;If you&#39;re new into the problem space and datasets, make sure you allocate time to get up to speed with the data - depending on the complexity this could be anywhere from a few hours to a few days. If possible, include domain experts or people who are already familiar with the data structures and format on the team (or at least have a line of communication to them) to help answer questions that you will inevitably come up with as you get started.&lt;/p&gt;
&lt;h4 id=&quot;byosql-(if-you-can!)&quot;&gt;BYOSQL (if you can!) &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#byosql-(if-you-can!)&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;One thing that is going to dramatically accelerate you is &lt;em&gt;bringing your SQL&lt;/em&gt; from existing systems - table definitions (DDL), stored procedures, analytical queries.. anything you or existing users are already using is going to really help in the build out of the platform. It doesn&#39;t matter what system this is either, just make sure to allocate some time to make a few changes between the SQL code you have and T-SQL for Synapse as part of your sprint.&lt;/p&gt;
&lt;h4 id=&quot;reports-and-dashboards-complete-the-vision&quot;&gt;Reports and dashboards complete the vision &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#reports-and-dashboards-complete-the-vision&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;There has been a lot of effort in recent years to make visualisation and reporting tools easy to use, and whatever tooling you have access to should make it relatively straight forward to build some impactful visualisations once the data model is in place. Being able to make good reports relies on a good understanding of the the &lt;em&gt;data structure&lt;/em&gt; and clarity on the &lt;em&gt;questions you or the user should be able to answer&lt;/em&gt; with the reports.&lt;/p&gt;
&lt;p&gt;When thinking about what to build and how to build it, I generally recommend working with pen and paper - consider the questions you are looking to answer with the data, and start to think about what metrics and comparisons would be useful to see. Sketch out a low fidelity prototype (&lt;a href=&quot;https://en.wikipedia.org/wiki/Website_wireframe&quot;&gt;wireframe drawings&lt;/a&gt; are great for this) of the visualisation types, data, and overall report layout that you want to build to answer your questions. Once you&#39;ve got something you&#39;re happy with, you can jump to the keyboard and start to build. This can be a great collaborative exercise if you&#39;re working as part of a team. You can also use these prototypes to walk your key stakeholders or sponsor users through what you&#39;re intending to build to get input and sign off (if required). This will radically reduce the time it takes to build your reports, and validate your thinking before you invest in implementing anything.&lt;/p&gt;
&lt;h4 id=&quot;don&#39;t-leave-without-a-map&quot;&gt;Don&#39;t leave without a map &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#don&#39;t-leave-without-a-map&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;One of the key things to remember with any technical project is to &lt;em&gt;plan ahead, know and agree the aims and outcomes, and review progress against this plan regularly as you go&lt;/em&gt;. Use the agreed scope and outcomes as your north star, as this is what you will be evaluating the outputs against when you come to the end of the sprint - you don&#39;t want to scupper the success of the team by getting sidetracked! Don&#39;t forget to also capture all the fun ideas you have along the way of things you could add - by capturing them you create an opportunity for these to be included in a future sprint.&lt;/p&gt;
&lt;h2 id=&quot;technical&quot;&gt;Technical &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#technical&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/solution-ideas/articles/modern-data-warehouse#data-flow&quot;&gt;flow of data&lt;/a&gt; through your MVP is likely to follow this common pattern. The focus of this post is on how to move through steps 1 to 3.&lt;/p&gt;
&lt;img class=&quot;img-fluid-blog&quot; src=&quot;https://joeplumb.com/img/2.png&quot; alt=&quot;Architecture diagram of a Modern Data Warehouse&quot; /&gt;
&lt;p&gt;&lt;em&gt;Architecture overview of data flow for Modern Data Warehouse pattern in Azure&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At a high level, you&#39;re going to load a representative, raw sample of data from your source systems into Azure, use that to define and build your processing and transformation jobs and pipelines, build your target database structure in Synapse, then load your processed data into the warehouse. After that, you can work on connecting directly to the source systems for the full data load, and parameterizing your pipelines by implementing a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/data-factory/how-to-create-tumbling-window-trigger&quot;&gt;tumbling window&lt;/a&gt; approach to incrementally load new data.&lt;/p&gt;
&lt;p&gt;I&#39;ve collated my notes on things you might want to consider and technical decisions you&#39;re going to have to make at each of these stages as you build out your use case.&lt;/p&gt;
&lt;h4 id=&quot;landing-your-data&quot;&gt;Landing your data &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#landing-your-data&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To get you going, manually land a representative sample from your source systems into your storage account in the quickest way you can. &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/vs-azure-tools-storage-manage-with-storage-explorer&quot;&gt;Azure Storage Explorer&lt;/a&gt; is a really useful tool to copy data to your cloud storage from your local machine. It&#39;s recommended to use folders to separate your data between layers and sources - for now, land the data as-is into a &lt;code&gt;Raw&lt;/code&gt; folder, with a folder for each source. Maintain any other partitioning/folder/file structure that the data has - you&#39;ll need this to be the same structure as if you got it from your source system in order to make sure the processes that you develop will continue to work once you hook up to your live system.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/Raw/Source1/2020/11/01/file.extension
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Example folder structure in the Raw layer of your lake&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As you process the data, write it back out into the lake into different folders - you&#39;ll typically have at least a &lt;code&gt;Cleansed&lt;/code&gt; and &lt;code&gt;Curated&lt;/code&gt; layer in your lake which represents your datasets as you tidy them up, join them together, and apply more structure and value. Writing data back out into different layers means you can store all of your data at the lowest cost possible, re-process data from your &lt;code&gt;Raw&lt;/code&gt; layer if necessary (e.g. to add in additional columns that were previously thought of as unnecessary), and provide analysts or data scientists access to your data for advanced analytics workloads, if appropriate. You can find more guidance on &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/storage/blobs/data-lake-storage-best-practices#batch-jobs-structure&quot;&gt;structuring your data lake&lt;/a&gt; for batch processing here. Data lake design is beyond the scope of this article, but &lt;a href=&quot;https://medium.com/@Nicholas_Hurt/building-your-data-lake-on-adls-gen2-3f196fc6b430&quot;&gt;this blog post&lt;/a&gt; written by my colleauge Nick Hurt goes into more detail of Data Lake organisation and design, too.&lt;/p&gt;
&lt;h4 id=&quot;data-engineering-and-data-structure&quot;&gt;Data engineering and data structure &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#data-engineering-and-data-structure&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;There are different ways to do this depending on how you like to work and your programming skills - you can write SQL scripts and transform data using SQL Serverless or your SQL pool in Azure Synapse, take a no-code approach using Mapping Data Flows, or code your transformations in SQL, Scala, PySpark, or R, to run in Azure Databricks or Synapse Spark pools. Your choice will also depend on the complexity of the transformations you need to perform. My recommendation here is to &lt;em&gt;go with what feels comfortable to you&lt;/em&gt; - there are comprehensive guides to getting started with each of the above approaches that I&#39;ve linked to at the bottom of this article.&lt;/p&gt;
&lt;p&gt;Things you&#39;ll be thinking about as you process your data are standardising on common formats, removing unnecessary columns, and applying cleaning rules like data standardization (e.g. set names to all capital letters) and data validation (e.g. validate product or customer IDs). This should be pretty easy if your data is already in a relational format and you&#39;re extracting in flat files (like &lt;code&gt;csv&lt;/code&gt;s) - if your data is coming in in non-relational formats like &lt;code&gt;json&lt;/code&gt; or &lt;code&gt;xml&lt;/code&gt;, this may require you to unpack nested data which you can do using &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/data-factory/format-json#mapping-data-flow-properties&quot;&gt;Mapping Data Flows&lt;/a&gt; or &lt;a href=&quot;https://docs.microsoft.com/en-us/learn/modules/transform-data-with-dataframes-apache-spark-pools-azure-synapse-analytics/6-flatten-nested-structures-explode-arrays&quot;&gt;Spark&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One thing you may want to think about at this stage is how you intend to manage updates to data in your analytical store. If you want to persist history and changes to data over time, you can rely on the tried and tested method of Slowly Changing Dimensions.&lt;/p&gt;
&lt;h4 id=&quot;slowly-changing-what%3F&quot;&gt;Slowly changing what? &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#slowly-changing-what%3F&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Slowly changing dimensions! These are a set of patterns that enable you to track history and changes in your data. There are &lt;a href=&quot;https://en.wikipedia.org/wiki/Slowly_changing_dimension&quot;&gt;different types of slowly changing dimensions&lt;/a&gt; - you can implement logic to process these as you load new data into your warehouse, and &lt;a href=&quot;https://www.youtube.com/watch?v=tc283k8CWh8&quot;&gt;templates are included to do this with Mapping Data Flows&lt;/a&gt; out of the box, too.&lt;/p&gt;
&lt;p&gt;NB: This is not a requirement - this is an MVP after all! You can always come back and add this logic into your processing pipelines later on.&lt;/p&gt;
&lt;h4 id=&quot;distributing-data-in-synapse&quot;&gt;Distributing data in Synapse &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#distributing-data-in-synapse&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You&#39;ll also have to think about how the data is distributed in Synapse to optimize the operations your tables across the warehouse - depending on the data volumes and purpose of your table, there are different choices; round-robin and hash distributed tables, or replicated tables.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Round-robin&lt;/em&gt; distribution eveny and randomly distributes the data across all distributions. Loading data into round robin tables is fast, but query performance can be impacted as they can require more data movement. Consider round robin as a  staring point, for tables that have no obvious column for hash distributing - it&#39;s also good for staging your data in Synapse before inserting to your production tables.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Hash&lt;/em&gt; distribution is used when it makes sense to group data in a distriubtion based on a certain value, minimizing data movement and improving query performance. You could distribiute on any column, perhaps country code, product line, or userid. You&#39;ll need relatively high cardinality for a column hash distribution (100s or 1000s of values), and the table size on disk is more than 2 GB.&lt;/li&gt;
&lt;li&gt;For smaller, less frequently changing tables (e.g. dimensions) you might consider instead using &lt;em&gt;replicated tables&lt;/em&gt;. This means a replica of the data will be stored in each distribution, saving shuffling of data which would degrade performance. Generally, 2GB or less is considered small, replicable table.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can read more about &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/synapse-analytics/sql/develop-tables-overview#distributed-tables&quot;&gt;distributed tables&lt;/a&gt; and &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/synapse-analytics/sql-data-warehouse/sql-data-warehouse-tables-distribute&quot;&gt;design guidance&lt;/a&gt; in then documentation. This &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/synapse-analytics/sql-data-warehouse/cheat-sheet&quot;&gt;cheat sheet&lt;/a&gt; also gives a fantastic starter for designing your warehouse.&lt;/p&gt;
&lt;p&gt;My final comment on this would be not to get too bogged down in the details here. Nothing that you build here will be fixed in stone, and you can expect to refine the decisions made as you address any performance issues at a later stage.&lt;/p&gt;
&lt;h4 id=&quot;loading-from-the-lake-into-synapse&quot;&gt;Loading from the lake into Synapse &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#loading-from-the-lake-into-synapse&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Once your data is prepared and in the right structure, and your target tables are created in your Synapse SQL pool, load your data from your storage account into Synapse &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/data-factory/load-azure-sql-data-warehouse#load-data-into-azure-synapse-analytics&quot;&gt;using the copy activity&lt;/a&gt;. It&#39;s best practice to load your data into staging tables before inserting into production tables. Within the settings of your activity, you&#39;ll find an option of the function that should be used; PolyBase and the COPY command both use a parallel load routine, making them very efficient for big loads. Bulk insert is essentially batched &lt;code&gt;INSERT&lt;/code&gt; statements of your data, so PolyBase or COPY are the preferred choices here.&lt;/p&gt;
&lt;h4 id=&quot;security-and-authentication&quot;&gt;Security and Authentication &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#security-and-authentication&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You can authorize users into Synapse with 2 authorization types; Azure Active Directory (AAD) Authentication, or SQL Authentication. There are &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/synapse-analytics/sql/sql-authentication?tabs=provisioned#non-administrator-users&quot;&gt;a few steps to follow in order to enable Active Directory authentication&lt;/a&gt;, and these steps need to be performed by an administrator. Its best practice to use AAD authentication, and while it might feel a little more onerous to set up, the additional security it affords you is worth it.&lt;/p&gt;
&lt;h4 id=&quot;other-performance-tuning-tricks-to-note&quot;&gt;Other performance tuning tricks to note &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#other-performance-tuning-tricks-to-note&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/synapse-analytics/sql-data-warehouse/performance-tuning-ordered-cci&quot;&gt;Ordered clustered column store indexes&lt;/a&gt; can be used to further improve performance - patterns where ordered CCI will help &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/synapse-analytics/sql-data-warehouse/performance-tuning-ordered-cci#query-performance&quot;&gt;are documented here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/synapse-analytics/sql/develop-materialized-view-performance-tuning&quot;&gt;Materialized views&lt;/a&gt; provide a low-friction mechanism for increasing performance by pre-calculating and maintaining the results of a view, rather than calculating the results at query time. This is all managed for you by Synapse, meaning the views get automatically refreshed as data gets loaded into the underlying tables. These can be a performant way to provide aggregations and calculations for downstream consumers.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/synapse-analytics/sql-data-warehouse/performance-tuning-result-set-caching&quot;&gt;Result set caching&lt;/a&gt; can also improve query performance and reduce compute resource usage. The result set cache has 1TB storage per database, and requires a user with the &lt;code&gt;dbmanager&lt;/code&gt; role to turn it on. The cache will be invalidated if the underlying data changes too - the oldest results get removed from the cache to make space for new ones. NB: row level security cannot be applied with Result Set Cache on.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/data-factory/concepts-data-flow-overview&quot;&gt;Getting started with Mapping Data Flows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/learn/modules/use-azure-synapse-serverless-sql-pools-for-transforming-data-lake/&quot;&gt;Getting started with data transformation with SQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/data-factory/solution-template-databricks-notebook&quot;&gt;Getting started with data transformation in Databricks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/building-analytical-data-systems-with-azure-synapse-a-rough-guide/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The above should help you understand what you need know when you&#39;re considering building out a data analytics capability using Azure Synapse (or any comparable technologies). If you&#39;re considering trialling this for yourself, I&#39;ve recently discovered this &lt;a href=&quot;https://azure.microsoft.com/en-us/resources/azure-synapse-analytics-proof-of-concept-playbook/&quot;&gt;useful guide&lt;/a&gt; that walks through the PoC process in a lot more detail that might be helpful, too.&lt;/p&gt;
&lt;p&gt;Please &lt;a href=&quot;https://bsky.app/profile/joeplumb.com&quot;&gt;message me on Bluesky&lt;/a&gt; if this has been helpful, or useless, or if you have any questions or comments!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Serverless functions with Azure and Python</title>
		<link href="https://joeplumb.com/blog/serverless-functions-with-azure-and-python/"/>
		<updated>2020-08-25T00:00:00Z</updated>
		<id>https://joeplumb.com/blog/serverless-functions-with-azure-and-python/</id>
		<content type="html">&lt;p&gt;I&#39;ve been working with Azure Functions since late last year - my first real opportunity to work with them came about during the &lt;a href=&quot;https://openhack.microsoft.com/&quot;&gt;App Modernization with NoSQL Openhack&lt;/a&gt;, where some of the exercises required attendees (i.e. me!) to implement an &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing&quot;&gt;event sourcing pattern&lt;/a&gt; in Azure. For so long before this point, I had been including them as a component in my architectures and systems design - how hard could they be?&lt;/p&gt;
&lt;h3 id=&quot;getting-started&quot;&gt;Getting Started &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/serverless-functions-with-azure-and-python/#getting-started&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The main hurdle for me with getting started was understanding the concepts that make up the function, and the boilerplate folder structure that comes out of the box when you initialize your project. Thankfully, one of the things it feels like there has been a great focus on with Azure Functions is the developer experience. Once I had my first couple of functions up and running, I began to find them incredibly intuitive. Conceptually, there are only 2 things you need to understand - triggers and bindings:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Triggers&lt;/em&gt; define what needs to happen in order to make your functions run. These could be events, requests, or a schedule. Each function has one trigger.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bindings&lt;/em&gt; are connections - I think of them as the services I want to &amp;quot;glue&amp;quot; my function to. You can have input and output bindings, and these can be the same or different types of services.&lt;/p&gt;
&lt;p&gt;I find it helpful to focus on the &lt;em&gt;data movement required to solve my problem&lt;/em&gt; - where does the data come from? Where does it need to go? What is the resulting action or output? From there, Inputs can be mapped into triggers (e.g. HTTP inputs) or bindings (e.g. fetch data from storage/a database), and similar for outputs (e.g. write the output to a datastore, return values over HTTP). Practical examples always help; let&#39;s take a look at building a function from scratch to understand these in more detail.&lt;/p&gt;
&lt;h4 id=&quot;prerequisites&quot;&gt;Prerequisites &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/serverless-functions-with-azure-and-python/#prerequisites&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Before you get started, you&#39;ll need to make sure you&#39;ve got a few tools installed. Firstly, you&#39;ll need a text editor - I recommend &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt; as I find it quick to work with and it&#39;s got a great ecosystem of extensions. You&#39;ll want to install a couple of these, namely &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions&quot;&gt;Azure Functions&lt;/a&gt; and one for the programming language you are working with, in our case, &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ms-python.python&quot;&gt;Python&lt;/a&gt;. You&#39;ll of course need Python installed (3.8, 3.7, and 3.6 are supported by Azure Functions). The final thing you&#39;ll need is &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=linux%2Ccsharp%2Cbash#install-the-azure-functions-core-tools&quot;&gt;Azure Functions Core Tools&lt;/a&gt;, which includes a version of the same runtime that powers Azure Functions, which enables you to run Functions in your development environment.&lt;/p&gt;
&lt;p&gt;With the above installed, you&#39;re all set!&lt;/p&gt;
&lt;h4 id=&quot;creating-your-first-function&quot;&gt;Creating your first function &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/serverless-functions-with-azure-and-python/#creating-your-first-function&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Start by creating a new directory, then open this in VS Code. When you&#39;re in your empty directory, open the command palette (&lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;shift&lt;/code&gt; + &lt;code&gt;p&lt;/code&gt;), then find and select &lt;em&gt;Azure Functions: Create Function&lt;/em&gt;. You&#39;ll then have to answer a series of prompts about the new function.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select a &lt;em&gt;language for your function project&lt;/em&gt;: Choose Python.&lt;/li&gt;
&lt;li&gt;Select a &lt;em&gt;Python alias to create a virtual environment&lt;/em&gt;: Choose the location of your Python interpreter. If the location isn&#39;t shown, type in the full path to your Python binary.&lt;/li&gt;
&lt;li&gt;Select a &lt;em&gt;template for your project&#39;s first function&lt;/em&gt;: Choose HTTP trigger.&lt;/li&gt;
&lt;li&gt;Provide a &lt;em&gt;function name&lt;/em&gt;: Leave this as HTTPTrigger1.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Authorization level&lt;/em&gt;: Choose Anonymous, which enables anyone to call your function endpoint. You can find more information about &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=python#authorization-keys&quot;&gt;access keys for functions here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You&#39;ll now see a bunch of files created in your once empty directory. To run the function locally, start debug mode in VS Code (Run &amp;gt; Start Debugging, or press F5), or type &lt;code&gt;func start&lt;/code&gt; in your terminal. This will start the local emulator for Azure Functions and enable you to test your code. See the output of the function by heading to the given url in your browser:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://localhost:7071/api/HTTPTrigger1?name=World
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I find this functionality to be really useful - it allows me to iterate on my code as I go and quickly validate the function is working as expected. But where does the code go? And how do you add or change the triggers and bindings for the function? Many files are generated when you create the templates, but there are only a handful of files you need focus on to begin with.&lt;/p&gt;
&lt;h4 id=&quot;the-three-files-you-need-to-know-about&quot;&gt;The three files you need to know about &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/serverless-functions-with-azure-and-python/#the-three-files-you-need-to-know-about&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Demystifying the files that are automatically generated by the extension will help you get to outcomes faster. The three files you should focus on first are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;local.settings.json&lt;/code&gt; - this is used by Azure Functions Core Tools to connect to &lt;em&gt;other services in Azure&lt;/em&gt; when you&#39;re running your functions project locally.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;function.json&lt;/code&gt; defines the triggers and bindings for your functions. These are very thoroughly documented in the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob&quot;&gt;triggers and bindings reference&lt;/a&gt;, and you can often re-use code from here with minimal changes. Notice that &lt;code&gt;$return&lt;/code&gt; is &lt;a href=&quot;https://github.com/joe-plumb/functions-demo/blob/main/HttpTrigger1/function.json#L17&quot;&gt;referenced in the output&lt;/a&gt; - this is the return value of the &lt;code&gt;main&lt;/code&gt; function. Using this, you can pass the output of your function to the output binding.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;__init__.py&lt;/code&gt; is where your code lives. If you&#39;ve added additional bindings on top of the starter code make sure to , &lt;a href=&quot;https://github.com/joe-plumb/functions-demo/blob/main/HttpTrigger2/__init__.py#L6&quot;&gt;reference them in the &lt;code&gt;main&lt;/code&gt; function&lt;/a&gt;, and you&#39;re away!&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;deploying-your-first-function&quot;&gt;Deploying your first function &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/serverless-functions-with-azure-and-python/#deploying-your-first-function&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When you&#39;re happy with your function, you&#39;re only a few steps away from deploying it to the cloud - the instructions below demonstrate how you can do this using the Azure Functions Extension in VS Code.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Choose the Azure icon in the Activity bar, then in the Azure: Functions area, choose the Deploy to function app... button.&lt;br /&gt;
&lt;img class=&quot;img-fluid&quot; src=&quot;https://joeplumb.com/img/1.png&quot; alt=&quot;a screenshot of Visual Studio Code, with the Azure Functions extension and deploy to azure buttons highlighted&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As before, follow the prompts and provide the following information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select &lt;em&gt;subscription&lt;/em&gt;: If prompted, select the subscription to use.&lt;/li&gt;
&lt;li&gt;Select &lt;em&gt;Function App in Azure&lt;/em&gt;: Choose &lt;code&gt;+ Create new Function App&lt;/code&gt;, or an existing one if you already have a service provisioned (NB: Deploying to Azure Functions with the extension will replace any existing functions with those that you push)&lt;/li&gt;
&lt;li&gt;Enter a &lt;em&gt;globally unique name for the function app&lt;/em&gt;: Type a valid name into the URL path (the name you type will be validated to make sure that it&#39;s unique).&lt;/li&gt;
&lt;li&gt;Select a &lt;em&gt;runtime&lt;/em&gt;: Choose the same version of Python you&#39;ve been working on locally.&lt;/li&gt;
&lt;li&gt;Select a &lt;em&gt;location&lt;/em&gt;: For better performance, choose a region near you.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The deployment will complete in the background - you can follow the progress by clicking the &amp;quot;Stream Logs&amp;quot; button on the prompt in VS Code. A notification will be displayed once the function app is created and deployed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;That&#39;s it! Your function is now deployed to Azure. You can see it in action by going to the function URL and passing parameters, just like we did with the local emulator, e.g.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;http://$YOUR_FUNCTIONS_SERVICE_NAME.azurewebsites.net/api/HTTPTrigger1?name=World
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&#39;s a more comprehensive set of instructions (with more screenshots too) &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-vs-code?pivots=programming-language-python&quot;&gt;available in the documentation&lt;/a&gt;, if you&#39;d like more details on any of the above steps.&lt;/p&gt;
&lt;h3 id=&quot;taking-it-further&quot;&gt;Taking it further &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/serverless-functions-with-azure-and-python/#taking-it-further&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;ve got this far, you should be good to go! I&#39;ve included some links and a summary of some other projects and repositories that might help you get started with your use case below.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Event sourcing with Cosmos DB and the Change Feed&lt;/em&gt; - As part of the Openhack that got me started, I pushed some starter functions for working with Cosmos DB to &lt;a href=&quot;https://github.com/joe-plumb/jp-azf-samples&quot;&gt;this repo&lt;/a&gt;. Check this out if you&#39;re looking for some examples of getting data from &lt;a href=&quot;https://github.com/joe-plumb/jp-azf-samples/tree/main/EventHubToCosmosDB&quot;&gt;Event Hubs to Cosmos DB&lt;/a&gt;, or working with the &lt;a href=&quot;https://github.com/joe-plumb/jp-azf-samples/tree/main/CosmosChangeFeedTrigger&quot;&gt;Change&lt;/a&gt; &lt;a href=&quot;https://github.com/joe-plumb/jp-azf-samples/tree/main/CosmosDocumentUpdateOnTrigger&quot;&gt;Feed&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Creating Serverless APIs to share data from Azure Blob&lt;/em&gt; - After sharing this getting started content with my team, a colleague of mine asked whether Azure Functions would be a good fit to build a REST API for data sharing. Using a HTTP trigger and Azure Blob storage input binding, I was able to knock together &lt;a href=&quot;https://github.com/joe-plumb/serverless-data-api&quot;&gt;this example&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;End to end tutorial&lt;/em&gt; - This post covers the basics, and should cover all you need to know to get started. If you want to go into more detail, there is a brilliant end to end &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/developer/python/tutorial-vs-code-serverless-python-01&quot;&gt;getting started tutorial in the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;on-my-to-do-list&quot;&gt;On my to do list &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/serverless-functions-with-azure-and-python/#on-my-to-do-list&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One pattern I haven&#39;t yet had the opportunity to road test is the integration between &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/machine-learning/how-to-deploy-functions&quot;&gt;Azure Machine Learning and Azure Functions&lt;/a&gt;. I can see this being a really powerful pattern for peaky, unpredictable workloads, or for any applications where deploying to Kubernetes would add unnecessary complexity.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://joeplumb.com/blog/serverless-functions-with-azure-and-python/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this post, I&#39;ve covered how to get started with Azure Functions and Python, including some references to other useful material and some GitHub repositories for examples. Let me know if this was useful, good luck and happy building!&lt;/p&gt;
&lt;p&gt;- Joe&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Has this helped you get up to speed with serverless? Have you deployed or do you want to know how to deploy ML models to the cloud using functions? &lt;a href=&quot;https://twitter.com/joe_plumb&quot;&gt;Contact me on Twitter&lt;/a&gt; if so, or if you have any questions or comments!&lt;/em&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>✨Hey world!✨</title>
		<link href="https://joeplumb.com/blog/hello-world/"/>
		<updated>2020-07-31T00:00:00Z</updated>
		<id>https://joeplumb.com/blog/hello-world/</id>
		<content type="html">&lt;p&gt;&lt;em&gt;Note: this blog was originally hosted as an Angular app - we are now live with &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;11ty&lt;/a&gt; - woo!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here we are then! First blog post going live in true mvp style. I am still adding to the structure, formatting and pipelines for the site, and am still &lt;a href=&quot;https://github.com/joe-plumb/joeplumbcom/issues/3&quot;&gt;missing some basic functionality&lt;/a&gt; and styling, so expect this post to evolve over the next week or so.&lt;/p&gt;
&lt;p&gt;I thought quite a lot about how I wanted this to come together and what tech stack I wanted to use. I took a look at some static JAM-stack frameworks like &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;11ty&lt;/a&gt; and &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt;, but eventually decided on Angular for a few of reasons - I wanted something that had a server-side component as I want to have the option of developing my own and integrating with other services, porting my existing Bootstrap site into Angular seemed relatively straight forward (it was! I was plesantly surprised &lt;a href=&quot;https://twitter.com/joe_plumb/status/1290337857274609665?s=20&quot;&gt;for a Javascript noob&lt;/a&gt;), and, I found &lt;a href=&quot;https://medium.com/@david.dalbusco/add-a-blog-to-your-angular-website-using-markdown-files-31cdb0627bdd&quot;&gt;this useful blog&lt;/a&gt; that walked through how to do just what I was looking to achieve. Quite meta .. a blog about building a blog based on a blog post about building blogs .. but here we are.&lt;/p&gt;
&lt;p&gt;My aim is simple. Here, I will document my learnings and thoughts about technology - predominantly &lt;em&gt;analytics&lt;/em&gt;, &lt;em&gt;big data&lt;/em&gt;, &lt;em&gt;machine learning&lt;/em&gt;, and probably some &lt;em&gt;coffee&lt;/em&gt; too - things I certainly spend a lot of time thinking about, and, in time, who knows, they may be of use to someone else!&lt;/p&gt;
&lt;p&gt;So it&#39;s not perfect, and there are many like it, but this blog is truly ✨mine✨. And I&#39;m looking forward to continuing to chart my path and document my journey!&lt;/p&gt;
&lt;p&gt;- Joe&lt;/p&gt;
</content>
	</entry>
</feed>
