<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eduardo Rabelo</title>
    <description>The latest articles on DEV Community by Eduardo Rabelo (@oieduardorabelo).</description>
    <link>https://dev.to/oieduardorabelo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F95913%2F093483ee-40d3-4ad2-9dbd-7a2eb57b020d.jpg</url>
      <title>DEV Community: Eduardo Rabelo</title>
      <link>https://dev.to/oieduardorabelo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oieduardorabelo"/>
    <language>en</language>
    <item>
      <title>Declarative Automation Bundle: Bind an Existing Lakeflow Job</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Tue, 25 Aug 2026 13:44:26 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/declarative-automation-bundle-bind-an-existing-lakeflow-job-54cm</link>
      <guid>https://dev.to/oieduardorabelo/declarative-automation-bundle-bind-an-existing-lakeflow-job-54cm</guid>
      <description>&lt;p&gt;&lt;em&gt;A matching YAML configuration file does not adopt the Lakeflow Job already in your workspace. Bind the bundle resource key to that Lakeflow Job, then let the first deployment update the right ID.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Suppose &lt;code&gt;refresh-orders&lt;/code&gt; already runs in production every morning.&lt;/p&gt;

&lt;p&gt;An engineer created it manually in the Databricks workspace user interface months ago. It has run history, notifications, a schedule, and a notebook that somebody still edits by hand.&lt;/p&gt;

&lt;p&gt;We want the Lakeflow Job in Git. We do not want a second Lakeflow Job, a changed schedule, or a first deployment from somebody's laptop.&lt;/p&gt;

&lt;p&gt;The risky step is not generating YAML configuration. It is the first &lt;code&gt;databricks bundle deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A Declarative Automation Bundle tracks deployed objects by ID in a state file in the Databricks workspace. The Databricks CLI does not match a Lakeflow Job by name. If the state file does not map the resource key to the existing Lakeflow Job ID, the Databricks CLI treats the Lakeflow Job as new.&lt;/p&gt;

&lt;p&gt;This article adopts one existing scheduled Lakeflow Job into one production bundle target. It has only workspace notebook tasks and no paginated collection with more than 100 items.&lt;/p&gt;

&lt;p&gt;I am not moving Lakeflow Jobs between bundles. I am not designing continuous integration, changing &lt;code&gt;run_as&lt;/code&gt;, or hardening the Lakeflow Job. Those are separate changes after this adoption succeeds.&lt;/p&gt;

&lt;p&gt;Use this only after the bundle passes its development or user acceptance testing suite and existing production release controls. The Lakeflow Job and target already meet Manuka standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four values that must agree
&lt;/h2&gt;

&lt;p&gt;Before I run a bundle command, I write down four values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the existing Lakeflow Job ID&lt;/li&gt;
&lt;li&gt;the bundle resource key&lt;/li&gt;
&lt;li&gt;the target name&lt;/li&gt;
&lt;li&gt;the identity that will deploy this target later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our example uses these values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lakeflow Job ID: 6565621249
Resource key: refresh_orders
Target:       prod
Deployer:     the production release service principal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resource key is the YAML configuration identifier under &lt;code&gt;resources.jobs&lt;/code&gt;. It is not the Lakeflow Job display name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# resources/refresh_orders.job.yml (fragment, not deployable alone)&lt;/span&gt;
&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;refresh_orders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;refresh-orders&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key identifies the bundle declaration. &lt;code&gt;name&lt;/code&gt; is the Lakeflow Job name engineers see in the Databricks workspace.&lt;/p&gt;

&lt;p&gt;The target selects a workspace and its deployment state file. By default, the Declarative Automation Bundle uses this workspace root path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Workspace/Users/${workspace.current_user.userName}/.bundle/${bundle.name}/${bundle.target}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the &lt;code&gt;refresh_team&lt;/code&gt; bundle in the &lt;code&gt;prod&lt;/code&gt; target, a release service principal might use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Workspace/Users/deploy-prod-&amp;lt;UUID&amp;gt;/.bundle/refresh_team/prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A developer named Ana uses a different path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Workspace/Users/ana@example.com/.bundle/refresh_team/prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deployer part changes, so the service principal and Ana use separate deployment state files.&lt;/p&gt;

&lt;p&gt;That identity detail changes the adoption sequence. I bind with the same production service principal that will run future production deployments. A bind through a developer's profile records the mapping in that developer's state file. The continuous integration workflow then sees no mapping and can still plan a create.&lt;/p&gt;

&lt;p&gt;Databricks recommends a service principal for production deployments. It also recommends granting write access to the production root path only to that principal. Do not use &lt;code&gt;/Shared&lt;/code&gt; for that path.&lt;/p&gt;

&lt;p&gt;Binding has one narrow effect. It records that &lt;code&gt;refresh_orders&lt;/code&gt; refers to Lakeflow Job &lt;code&gt;6565621249&lt;/code&gt; in this target's state file. It does not apply the YAML configuration to the Lakeflow Job until the next deployment.&lt;/p&gt;

&lt;p&gt;The key, target, and ID each answer a different question:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Question it answers&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Resource key&lt;/td&gt;
&lt;td&gt;Which YAML configuration declaration is this?&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refresh_orders&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;Which workspace state file receives the mapping?&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prod&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lakeflow Job ID&lt;/td&gt;
&lt;td&gt;Which existing Lakeflow Job does it manage?&lt;/td&gt;
&lt;td&gt;&lt;code&gt;6565621249&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not replace the ID with the Lakeflow Job name. A Declarative Automation Bundle stores resource IDs in the workspace state file. It does not use the display name to discover an existing Lakeflow Job.&lt;/p&gt;

&lt;p&gt;The deploying identity also needs permission to manage the existing Lakeflow Job. &lt;code&gt;CAN_MANAGE&lt;/code&gt; is the Lakeflow Job-level permission that allows an identity to edit the definition and schedule. Confirm that access before the pull request reaches the bind step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture the Lakeflow Job before you turn it into code
&lt;/h2&gt;

&lt;p&gt;I capture the remote definition first. This gives the pull request something concrete to compare with the generated files.&lt;/p&gt;

&lt;p&gt;The identity that captures or generates the Lakeflow Job needs &lt;code&gt;CAN_VIEW&lt;/code&gt; on the production Lakeflow Job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;JOB_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6565621249

databricks &lt;span class="nb"&gt;jobs &lt;/span&gt;get &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JOB_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--include-trigger-state&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; refresh-orders.before.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command writes the Lakeflow Job definition and its trigger state to &lt;code&gt;refresh-orders.before.json&lt;/code&gt;. Store it in the pull request summary or ticket, not in the repository if it contains sensitive configuration.&lt;/p&gt;

&lt;p&gt;This walkthrough assumes the response contains every Lakeflow Job array on its first page. If &lt;code&gt;jobs get&lt;/code&gt; returns &lt;code&gt;next_page_token&lt;/code&gt;, I use a separate migration and capture every page.&lt;/p&gt;

&lt;p&gt;Next, I run the generator from the bundle project root. I keep its generated files uncommitted until the review is complete.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;databricks bundle generate job &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--existing-job-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JOB_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; refresh_orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generator creates a Lakeflow Job configuration under &lt;code&gt;resources/&lt;/code&gt;. It also downloads its Databricks workspace notebook files under &lt;code&gt;src/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The generated YAML configuration is evidence. It is not a reason to deploy immediately.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Run as&lt;/code&gt; is the identity the Lakeflow Job uses for its tasks. It is separate from the bundle's deploying identity.&lt;/p&gt;

&lt;p&gt;I compare the generated files with &lt;code&gt;refresh-orders.before.json&lt;/code&gt;. I check the Lakeflow Job name, task keys, notebook paths, schedule, pause state, parameters, notifications, and &lt;code&gt;Run as&lt;/code&gt;. I also check every file the Lakeflow Job references.&lt;/p&gt;

&lt;p&gt;This is where I stop unrelated improvements. A new timeout, adjusted retry count, or better notification list makes the first deployment harder to reason about. I usually open follow-up pull requests for those changes.&lt;/p&gt;

&lt;p&gt;If the Lakeflow Job uses Git source or a task type that the generator does not support, I stop this walkthrough here. I use a separate migration.&lt;/p&gt;

&lt;p&gt;The review has one simple rule: the first bundle YAML configuration must describe the Lakeflow Job we already operate. It is not the place to make it better.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Review item&lt;/th&gt;
&lt;th&gt;What I compare&lt;/th&gt;
&lt;th&gt;Why it matters now&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task graph&lt;/td&gt;
&lt;td&gt;Task keys and dependencies&lt;/td&gt;
&lt;td&gt;A missing task changes the Lakeflow Job's work.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;Notebook paths and downloaded files&lt;/td&gt;
&lt;td&gt;A path change can run different code.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trigger&lt;/td&gt;
&lt;td&gt;Cron expression, timezone, and pause state&lt;/td&gt;
&lt;td&gt;A changed trigger can skip or duplicate a scheduled run.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime settings&lt;/td&gt;
&lt;td&gt;Parameters, &lt;code&gt;Run as&lt;/code&gt;, and concurrency&lt;/td&gt;
&lt;td&gt;A setting can change who runs work or how often.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notifications&lt;/td&gt;
&lt;td&gt;Failure recipients and destinations&lt;/td&gt;
&lt;td&gt;The on-call team must keep receiving failures.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I keep &lt;code&gt;refresh-orders.before.json&lt;/code&gt; outside the generated &lt;code&gt;resources/&lt;/code&gt; and &lt;code&gt;src/&lt;/code&gt; directories. The saved response records the Lakeflow Job at adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate, bind, plan, then deploy
&lt;/h2&gt;

&lt;p&gt;I commit the generated configuration and downloaded source files before binding. Git now holds the proposed definition, but the Databricks workspace Lakeflow Job remains unmanaged.&lt;/p&gt;

&lt;p&gt;First, I validate the rendered production configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;databricks bundle validate &lt;span class="nt"&gt;--strict&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation checks the bundle configuration. It does not prove that the YAML configuration describes the existing Lakeflow Job. The comparison in the previous section does that work.&lt;/p&gt;

&lt;p&gt;Next, I bind the bundle resource key to the remote Lakeflow Job ID. The one-time adoption workflow uses &lt;code&gt;--auto-approve&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Binding is non-interactive and records state only. It does not apply the YAML configuration to the Lakeflow Job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;databricks bundle deployment &lt;span class="nb"&gt;bind &lt;/span&gt;refresh_orders &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JOB_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; prod &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use this one-time adoption exception because a useful plan needs the binding. An unbound plan can only show a create.&lt;/p&gt;

&lt;p&gt;The workflow publishes the summary and plan. I review both before approving the deployment. This exception does not change the Lakeflow Job during binding.&lt;/p&gt;

&lt;p&gt;Now I confirm the binding and inspect the first deployment plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;databricks bundle summary &lt;span class="nt"&gt;-t&lt;/span&gt; prod &lt;span class="nt"&gt;--force-pull&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json
databricks bundle plan &lt;span class="nt"&gt;-t&lt;/span&gt; prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The summary must map &lt;code&gt;refresh_orders&lt;/code&gt; to Lakeflow Job &lt;code&gt;6565621249&lt;/code&gt;. The plan must not show &lt;code&gt;create jobs.refresh_orders&lt;/code&gt;. If it does, I stop.&lt;/p&gt;

&lt;p&gt;An update is not itself a failure. &lt;code&gt;bundle generate job&lt;/code&gt; downloads notebooks and uses bundle paths. That can change a Lakeflow Job task's deployed notebook path.&lt;/p&gt;

&lt;p&gt;I verify every planned update against the capture. An unexplained change to the task graph, trigger, &lt;code&gt;Run as&lt;/code&gt;, parameters, or notifications stops the deployment.&lt;/p&gt;

&lt;p&gt;I review and approve that plan before the workflow runs the first deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;databricks bundle deploy &lt;span class="nt"&gt;-t&lt;/span&gt; prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this command through the release path. The first deploy changes the source of truth from workspace edits to the reviewed bundle configuration.&lt;/p&gt;

&lt;p&gt;The deliberate pause point sits between &lt;code&gt;bind&lt;/code&gt; and &lt;code&gt;deploy&lt;/code&gt;. Binding records the association but does not update the Lakeflow Job. I can still stop, fix the YAML configuration, or remove the association.&lt;/p&gt;

&lt;p&gt;If I bound the wrong key, ID, or target before the first deployment, I remove only the association:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;databricks bundle deployment unbind refresh_orders &lt;span class="nt"&gt;-t&lt;/span&gt; prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unbind leaves the Lakeflow Job in the Databricks workspace running. It only removes the link from this bundle state. I do not deploy while the YAML configuration remains unbound, because the next deployment would create a new Lakeflow Job.&lt;/p&gt;

&lt;p&gt;After the first deployment, unbind is not a configuration rollback. It does not restore the Lakeflow Job settings that the deploy applied.&lt;/p&gt;

&lt;p&gt;To recover with a bundle change, I bind the existing Lakeflow Job again, inspect the plan, then deploy the corrected YAML configuration. I use &lt;code&gt;refresh-orders.before.json&lt;/code&gt; as the pre-adoption reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  What fails when I skip the bind
&lt;/h2&gt;

&lt;p&gt;Consider the tempting shortcut. I generate YAML configuration, review it, and deploy without binding.&lt;/p&gt;

&lt;p&gt;The resource key is present in the bundle. The Lakeflow Job name might even match exactly. Neither fact gives the Databricks CLI the existing Lakeflow Job ID.&lt;/p&gt;

&lt;p&gt;The deployment state file has no mapping for &lt;code&gt;refresh_orders&lt;/code&gt;. The Databricks CLI therefore plans to create the Lakeflow Job declared in YAML configuration.&lt;/p&gt;

&lt;p&gt;That can leave two scheduled Lakeflow Jobs. One is the old Databricks workspace user interface-created Lakeflow Job. The other is the new bundle-created Lakeflow Job. Both can point at the same notebook and start work independently.&lt;/p&gt;

&lt;p&gt;Binding replaces that uncertainty with an explicit mapping. It says that this key manages this Lakeflow Job ID in this target. The next deployment updates that Lakeflow Job according to the reviewed configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern I recommend
&lt;/h2&gt;

&lt;p&gt;I use one adoption pull request for one existing Lakeflow Job in one target:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Capture the Lakeflow Job.&lt;/li&gt;
&lt;li&gt;Generate and review the configuration without improving it.&lt;/li&gt;
&lt;li&gt;Validate the target, bind the resource key, and inspect the plan.&lt;/li&gt;
&lt;li&gt;Deploy through the same release identity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Positives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The existing Lakeflow Job keeps its identity instead of being recreated.&lt;/li&gt;
&lt;li&gt;The first plan can expose an unintended Lakeflow Job change before deployment.&lt;/li&gt;
&lt;li&gt;Future deployments use the production service principal's bundle state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Negatives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The adoption needs production release credentials before the first bind.&lt;/li&gt;
&lt;li&gt;Manual workspace edits become drift and a later deploy can overwrite them.&lt;/li&gt;
&lt;li&gt;Each target needs its own review and binding to its own existing Lakeflow Job ID.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The negatives are worth accepting. A production Lakeflow Job should have one declared owner and one reviewed deployment path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the handover
&lt;/h2&gt;

&lt;p&gt;After deployment, I run these three read-only checks from the production release identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;databricks &lt;span class="nb"&gt;jobs &lt;/span&gt;get &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JOB_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; json
databricks bundle plan &lt;span class="nt"&gt;-t&lt;/span&gt; prod
databricks bundle summary &lt;span class="nt"&gt;-t&lt;/span&gt; prod &lt;span class="nt"&gt;--force-pull&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first command confirms that Lakeflow Job &lt;code&gt;6565621249&lt;/code&gt; still exists. The plan should not propose creating a Lakeflow Job. The summary reads the remote state file and gives the reviewer the Databricks workspace link for the managed Lakeflow Job.&lt;/p&gt;

&lt;p&gt;I watch the next scheduled run through the existing failure notification path. I expect one run from the same Lakeflow Job ID, using the same schedule. That is the first production measurement after the handover.&lt;/p&gt;

&lt;p&gt;Do not use this article for a Lakeflow Spark Declarative Pipeline, Lakeflow Connect gateway, or Lakeflow Job already tracked by another bundle. Each has different state and recovery concerns.&lt;/p&gt;

</description>
      <category>databricks</category>
      <category>cloud</category>
      <category>dataengineering</category>
      <category>dab</category>
    </item>
    <item>
      <title>Unity Catalog Schemas in Declarative Automation Bundles Are a Footgun</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Fri, 21 Aug 2026 15:06:29 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/unity-catalog-schemas-in-declarative-automation-bundles-are-a-footgun-3o3e</link>
      <guid>https://dev.to/oieduardorabelo/unity-catalog-schemas-in-declarative-automation-bundles-are-a-footgun-3o3e</guid>
      <description>&lt;p&gt;&lt;em&gt;Why &lt;code&gt;mode: development&lt;/code&gt; quietly renames your schemas, why the official escape hatch makes things worse for teams, and four safer patterns to structure your deployments instead.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Last week a teammate ran &lt;code&gt;databricks bundle deploy&lt;/code&gt; against our dev workspace.&lt;/p&gt;

&lt;p&gt;The command failed with an error about a schema that already existed.&lt;/p&gt;

&lt;p&gt;She had not touched that schema. Nobody had. Her code was identical to mine, and my deploy had worked minutes earlier.&lt;/p&gt;

&lt;p&gt;This guide walks you through why that happens.&lt;/p&gt;

&lt;p&gt;We start with the pieces, we watch the failure happen step by step, and then I show you four patterns that avoid the problem completely. One of them needs no extra tooling at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Declarative Automation Bundles in one minute
&lt;/h2&gt;

&lt;p&gt;A Declarative Automation Bundle, or DAB, is a set of YAML files in your repository that describes Databricks resources: jobs, pipelines, dashboards, schemas. You run &lt;code&gt;databricks bundle deploy&lt;/code&gt;, and the CLI creates or updates those resources on your workspace.&lt;/p&gt;

&lt;p&gt;It is infrastructure as code for the Databricks platform.&lt;/p&gt;

&lt;p&gt;The YAML lives in your repo next to your source code. Data engineers use it to ship work without clicking through the workspace UI. Deployment is manual, you run the command when you want it to run.&lt;/p&gt;

&lt;p&gt;Since CLI version 1.3.0, deploys run on the &lt;code&gt;direct&lt;/code&gt; engine by default. The CLI does not use Terraform anymore. The engine is the part of the CLI that talks to the Databricks APIs and applies your changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet our example bundle
&lt;/h2&gt;

&lt;p&gt;Everything in this guide uses one small bundle called &lt;code&gt;sales-analytics&lt;/code&gt;. It lives in two files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sales-analytics/
├── databricks.yml
└── resources/
    └── reporting.schema.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The top-level file names the bundle and holds the shared settings we will build up: variables and targets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# databricks.yml&lt;/span&gt;
&lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sales-analytics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema resource inside &lt;code&gt;resources/&lt;/code&gt; appears in the walkthrough section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two worlds with opposite rules
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;databricks bundle deploy&lt;/code&gt;, the CLI records what it created in a deployment state file.&lt;/p&gt;

&lt;p&gt;With an unmodified setup, that file lands in your user folder on the workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Workspace/Users/ana@gmail.com/.bundle/sales-analytics/dev/state/deployment.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That location is a default, not a law. You can move it with the &lt;code&gt;workspace.root_path&lt;/code&gt; setting, though most teams never change it. Production mode even validates that your paths do not point at one specific user.&lt;/p&gt;

&lt;p&gt;The state file records what &lt;em&gt;you&lt;/em&gt; deployed.&lt;/p&gt;

&lt;p&gt;My state file sits under my own email.&lt;/p&gt;

&lt;p&gt;Yours sits under yours.&lt;/p&gt;

&lt;p&gt;Two developers deploying the same repo never share a state file.&lt;/p&gt;

&lt;p&gt;Unity Catalog is Databricks' governance layer.&lt;/p&gt;

&lt;p&gt;A schema in Unity Catalog is a named container for tables and views, and it lives in a catalog.&lt;/p&gt;

&lt;p&gt;Here is the important part: a catalog and its schemas belong to the metastore, not to any person.&lt;/p&gt;

&lt;p&gt;If a schema named &lt;code&gt;reporting&lt;/code&gt; exists in catalog &lt;code&gt;main&lt;/code&gt;, every user sees that same &lt;code&gt;main.reporting&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is exactly one of it.&lt;/p&gt;

&lt;p&gt;So bundles mix two worlds with opposite rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State files&lt;/strong&gt; are private. One per developer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schemas&lt;/strong&gt; are global. One per metastore.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the time these worlds never collide, because development mode adds a safety layer between them.&lt;/p&gt;

&lt;p&gt;Let us look at that layer, because it is also where the footgun lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each deployment mode does
&lt;/h2&gt;

&lt;p&gt;A target is a named deployment environment, such as dev, uat, or prod. You pick one with the &lt;code&gt;-t&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;A well-built bundle has targets, and each target sets its mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# databricks.yml&lt;/span&gt;
&lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;uat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mode decides how a deploy behaves. Here is the full picture:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;mode: development&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;mode: production&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;mode unset (uat above)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resource name prefixing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes. Jobs get &lt;code&gt;[dev ana] nightly-ingest&lt;/code&gt;. Schemas get a sanitised form like &lt;code&gt;dev_ana_reporting&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No. Names deploy exactly as declared&lt;/td&gt;
&lt;td&gt;No. Names deploy exactly as declared&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schedules and triggers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All paused, regardless of your YAML&lt;/td&gt;
&lt;td&gt;Stay active as configured&lt;/td&gt;
&lt;td&gt;Stay active as configured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrent job runs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Forced open, up to four at once, for fast iteration&lt;/td&gt;
&lt;td&gt;As configured&lt;/td&gt;
&lt;td&gt;As configured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment lock&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Disabled for faster iteration&lt;/td&gt;
&lt;td&gt;Enabled&lt;/td&gt;
&lt;td&gt;Enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Guardrails&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None. Built for speed&lt;/td&gt;
&lt;td&gt;Checks the git branch when you pin one, expects &lt;code&gt;run_as&lt;/code&gt; and &lt;code&gt;permissions&lt;/code&gt;, rejects user-specific paths&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use it for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Personal sandboxes in a shared workspace&lt;/td&gt;
&lt;td&gt;Shared environments like uat and prod&lt;/td&gt;
&lt;td&gt;Simple bundles or single-deployer setups&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The schema prefix is not the pretty bracketed string. Schema names allow only letters, numbers, and underscores.&lt;/p&gt;

&lt;p&gt;So the CLI removes brackets and spaces: &lt;code&gt;[dev ana]&lt;/code&gt; becomes &lt;code&gt;dev_ana_&lt;/code&gt;, and my schema &lt;code&gt;reporting&lt;/code&gt; lands as &lt;code&gt;dev_ana_reporting&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Second, the prefix comes from your identity, so every developer gets their own copy. That is isolation working as designed, and it only happens where humans deploy side by side.&lt;/p&gt;

&lt;p&gt;So far so good... right?&lt;/p&gt;

&lt;h2&gt;
  
  
  The footgun
&lt;/h2&gt;

&lt;p&gt;Your notebooks and pipelines usually expect a stable schema name. Maybe a query hardcodes &lt;code&gt;main.reporting&lt;/code&gt;. After a dev-mode deploy, that schema is called &lt;code&gt;dev_ana_reporting&lt;/code&gt;, and the query breaks.&lt;/p&gt;

&lt;p&gt;This surprises everyone the first time. It looks like a bug. It is not: it is the safety layer doing its job.&lt;/p&gt;

&lt;p&gt;The obvious next move is to search the docs, find an escape hatch, and switch it off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# databricks.yml&lt;/span&gt;
&lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;skip_name_prefix_for_schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flag does exactly what it says. Jobs keep their prefix. Schemas lose theirs. Every developer now deploys a schema called plain &lt;code&gt;reporting&lt;/code&gt; into catalog &lt;code&gt;main&lt;/code&gt;, no matter who runs the deploy.&lt;/p&gt;

&lt;p&gt;It feels harmless, but it is not!&lt;/p&gt;

&lt;p&gt;Remember our two worlds: private state files, global schemas.&lt;/p&gt;

&lt;p&gt;With the flag on, every developer's private state file claims the same public object.&lt;/p&gt;

&lt;p&gt;Here is what happens next:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ana deploys first.&lt;/strong&gt; The CLI creates schema &lt;code&gt;main.reporting&lt;/code&gt;. No prefix. Ana's state file records that she created it. The deploy succeeds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ben deploys ten minutes later.&lt;/strong&gt; His state file is empty, so the CLI tries to create the schema fresh. It sends a create request for &lt;code&gt;main.reporting&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Databricks refuses.&lt;/strong&gt; The schema already exists, and Ben does not own it. On the direct engine the error names the resource and carries the API message, something like &lt;code&gt;Error: cannot create resources.schemas.reporting: Schema 'main.reporting' already exists&lt;/code&gt;. Either way, his deploy fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ben asks Ana to delete her deployment so he can test.&lt;/strong&gt; Ana runs &lt;code&gt;databricks bundle destroy -t dev&lt;/code&gt;. Bundles always force-delete schemas they manage, warning first that underlying data may be lost. Her state file says she owns &lt;code&gt;main.reporting&lt;/code&gt;, so the CLI drops it, tables included. The schema vanishes for everyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Now Ben deploys successfully&lt;/strong&gt; and owns the very schema Ana needs. When Ana redeploys, she hits the same wall Ben did.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The second hazard: toggling the flag
&lt;/h3&gt;

&lt;p&gt;There is a second hazard that most people miss.&lt;/p&gt;

&lt;p&gt;Inside a bundle, a resource's name is its identity. Change the effective name, and the CLI treats it as a brand-new resource.&lt;/p&gt;

&lt;p&gt;So if you deploy with prefixes, then add the flag later, the CLI creates unprefixed schemas and deletes the prefixed ones.&lt;/p&gt;

&lt;p&gt;Your data goes through that cycle too.&lt;/p&gt;

&lt;p&gt;If you switch this flag on or off, the next deploy can delete schemas and their data. It is not a small settings change.&lt;/p&gt;

&lt;p&gt;One reassurance before the patterns: this failure is a development-mode story.&lt;/p&gt;

&lt;p&gt;Most teams deploy to uat and prod from a build system using one robot account, called a service principal. One robot account means no collision.&lt;/p&gt;

&lt;p&gt;I lived through version one of this story... taking turns failing!&lt;/p&gt;

&lt;p&gt;Do not reach for the flag. Reach for one of these four patterns instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: Accept the prefix and plan your code around it
&lt;/h2&gt;

&lt;p&gt;The simplest pattern needs zero extra tooling. Leave everything as Databricks designed it, and make your code read the schema name at runtime instead of hardcoding it.&lt;/p&gt;

&lt;p&gt;The trick is a prefix variable with a different value per target.&lt;/p&gt;

&lt;p&gt;Dev derives it from your username, uat uses a fixed string, and prod uses none. The &lt;code&gt;short_name&lt;/code&gt; substitution is your username without the domain, so &lt;code&gt;ana@gmail.com&lt;/code&gt; becomes &lt;code&gt;ana&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# databricks.yml&lt;/span&gt;
&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;catalog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Catalog for this deployment.&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;schema_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix for schema names in this environment.&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;

&lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;schema_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev_${workspace.current_user.short_name}_&lt;/span&gt;
  &lt;span class="na"&gt;uat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;schema_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uat_&lt;/span&gt;
  &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every consumer of the schema builds its name from that variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# resources/ingest.job.yml&lt;/span&gt;
&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ingest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;source_catalog&lt;/span&gt;
          &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${var.catalog}&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;source_schema&lt;/span&gt;
          &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${var.schema_prefix}reporting&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One YAML file, three outcomes. Ana deploying to dev passes her notebook &lt;code&gt;dev_ana_reporting&lt;/code&gt;. The same job deployed to uat reads &lt;code&gt;uat_reporting&lt;/code&gt;. In prod it reads plain &lt;code&gt;reporting&lt;/code&gt;. Your notebook calls &lt;code&gt;dbutils.widgets.get("source_schema")&lt;/code&gt; and never cares which stage triggered the run.&lt;/p&gt;

&lt;p&gt;The schema resource itself keeps its plain name, &lt;code&gt;name: reporting&lt;/code&gt;. The CLI adds its own prefix on top in dev.&lt;/p&gt;

&lt;p&gt;Never put &lt;code&gt;${var.schema_prefix}&lt;/code&gt; inside the resource name. The CLI would stack its prefix on top of yours, and you would get a schema called &lt;code&gt;dev_ana_dev_ana_reporting&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Positives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nothing new to build, host, or permission. It is pure YAML you already have.&lt;/li&gt;
&lt;li&gt;Full lifecycle stays in one place. Deploy, redeploy, and destroy all work.&lt;/li&gt;
&lt;li&gt;Isolation is automatic. Nobody can collide with anybody.&lt;/li&gt;
&lt;li&gt;Destroy cleans up everything, including the schemas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Negatives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every consumer of the schema must take the name dynamically. Hardcoded queries break.&lt;/li&gt;
&lt;li&gt;You rely on the exact shape of the sanitised prefix, &lt;code&gt;dev_&amp;lt;username&amp;gt;_&lt;/code&gt;. It is stable today, but it is a convention, not a contract.&lt;/li&gt;
&lt;li&gt;Prefixed names look odd in Catalog Explorer and confuse newcomers.&lt;/li&gt;
&lt;li&gt;Some external tools cannot accept dynamic names, which blocks this pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick this pattern when your stack already passes configuration around, which good data pipelines do anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: Declare schemas outside the bundle
&lt;/h2&gt;

&lt;p&gt;Schemas often outlive the code that fills them. A natural split is to manage schemas somewhere else entirely: a Terraform module, a small provisioning script, or even a one-off manual setup for stable internal schemas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# schemas.tf&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"databricks_schema"&lt;/span&gt; &lt;span class="s2"&gt;"reporting"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;catalog_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"reporting"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your product bundle keeps deploying jobs and pipelines, and simply points at &lt;code&gt;main.reporting&lt;/code&gt;, which nobody's deploy mode can rename.&lt;/p&gt;

&lt;p&gt;Set permissions on the schema in the same Terraform module with a &lt;code&gt;databricks_grants&lt;/code&gt; resource, so access control travels with the schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Positives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stable, clean names in every environment. Code stays hardcoded and boring.&lt;/li&gt;
&lt;li&gt;The product bundle contains no schemas, so the prefix question never arises.&lt;/li&gt;
&lt;li&gt;Terraform brings plan previews and catches drift, which is when someone changes schemas outside the tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Negatives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two sources of truth. New joiners must learn where each thing lives.&lt;/li&gt;
&lt;li&gt;Manual or scripted setup drifts and does not scale across environments.&lt;/li&gt;
&lt;li&gt;Terraform adds state management, credentials, and a second tool to operate.&lt;/li&gt;
&lt;li&gt;Deleting the bundle leaves the schemas behind. Cleanup lives in the other tool now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick this pattern when a platform team already owns governance as code, or when schemas are genuinely shared infrastructure rather than project output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: Put schemas in their own bundle
&lt;/h2&gt;

&lt;p&gt;A middle path keeps everything as bundles but splits ownership. Create a second, tiny bundle that declares only the UC objects. Its targets never set &lt;code&gt;mode: development&lt;/code&gt;, so nothing ever gets prefixed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# sales-analytics-schemas/databricks.yml&lt;/span&gt;
&lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sales-analytics-schemas&lt;/span&gt;

&lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;uat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy it once per environment from CI using a service principal. Product developers never touch it, and because exactly one identity deploys it, the collision story from earlier cannot happen. Even the skip flag would be safe here, though you will not need it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Positives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stable names with full infrastructure-as-code discipline. Best of both worlds.&lt;/li&gt;
&lt;li&gt;One deployer identity removes the multi-developer hazard completely.&lt;/li&gt;
&lt;li&gt;Clear ownership boundary: platform owns schemas, teams own compute and code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Negatives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A second bundle means a second pipeline, repo or folder, and set of secrets.&lt;/li&gt;
&lt;li&gt;Ordering matters. Schemas must exist before product deploys reference them.&lt;/li&gt;
&lt;li&gt;Someone will eventually run it locally without noticing the missing dev mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick this pattern when you want IaC end to end and can afford a small platform-style repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: Give every developer a catalog
&lt;/h2&gt;

&lt;p&gt;If developers need clean schema names &lt;em&gt;and&lt;/em&gt; full self-service, flip the isolation up one level. Instead of letting dev mode rename schemas, give each developer their own catalog in dev, while uat and prod keep shared catalogs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# databricks.yml&lt;/span&gt;
&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;catalog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Catalog for this deployment.&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reporting&lt;/span&gt;

&lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;skip_name_prefix_for_schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;catalog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev_${workspace.current_user.short_name}&lt;/span&gt;
  &lt;span class="na"&gt;uat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;catalog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uat_reporting&lt;/span&gt;
  &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait, did I not just tell you to avoid that flag? Here it is safe, and the catalog is the reason.&lt;/p&gt;

&lt;p&gt;The flag removes the schema prefix, so the schema deploys as plain &lt;code&gt;reporting&lt;/code&gt;. Normally that invites collisions. In this pattern it cannot, because Ana deploys only into &lt;code&gt;dev_ana&lt;/code&gt; and Ben only into &lt;code&gt;dev_ben&lt;/code&gt;. The private catalog does the isolating that the prefix would otherwise do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Positives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean schema names and complete isolation at the same time.&lt;/li&gt;
&lt;li&gt;Deterministic per-person catalogs make debugging and cleanup easy.&lt;/li&gt;
&lt;li&gt;Works with existing code that expects fixed relative schema names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Negatives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each developer needs &lt;code&gt;CREATE CATALOG&lt;/code&gt; on the metastore, or a platform script must pre-provision catalogs.&lt;/li&gt;
&lt;li&gt;Catalogs accumulate. You need a naming convention and a cleanup habit.&lt;/li&gt;
&lt;li&gt;Grants must be set per catalog, which is more governance surface to maintain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick this pattern for teams that iterate fast on data products and want sandbox-per-developer ergonomics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing between them
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Stable names&lt;/th&gt;
&lt;th&gt;Dev isolation&lt;/th&gt;
&lt;th&gt;Extra infrastructure&lt;/th&gt;
&lt;th&gt;Main risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Accept the prefix&lt;/td&gt;
&lt;td&gt;No, dynamic&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Code must be parameterised everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schemas outside DAB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Via separate tooling&lt;/td&gt;
&lt;td&gt;Terraform or scripts&lt;/td&gt;
&lt;td&gt;Drift between two sources of truth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema-only DAB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Single deployer identity&lt;/td&gt;
&lt;td&gt;One more bundle and pipeline&lt;/td&gt;
&lt;td&gt;Deployment ordering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catalog per developer&lt;/td&gt;
&lt;td&gt;Yes, within own catalog&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;td&gt;Privileges and cleanup habits&lt;/td&gt;
&lt;td&gt;Catalog sprawl&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My default recommendation: start with Pattern 1 while the team is small, and graduate to Pattern 3 once a platform function exists.&lt;/p&gt;

&lt;p&gt;Reserve &lt;code&gt;skip_name_prefix_for_schema&lt;/code&gt; for two cases only: bundles that exactly one identity will ever deploy, such as a CI-owned pipeline, and the isolated personal catalogs in Pattern 4. Never toggle it on a bundle that already manages data without a migration plan.&lt;/p&gt;

&lt;p&gt;One warning that applies to all four patterns: switching between them renames schemas, and in a bundle a rename means delete and create. Migrate your data before you change approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Treat schemas as a boundary decision, not an accident of YAML layout.&lt;/p&gt;

&lt;p&gt;Accept the prefix and parameterise, move schemas to Terraform, give them a dedicated single-deployer bundle, or isolate at the catalog level.&lt;/p&gt;

&lt;p&gt;Any of the four beats an afternoon of two engineers taking turns breaking each other's workspace.&lt;/p&gt;

&lt;p&gt;Not sure where your bundle stands today? Run &lt;code&gt;databricks bundle plan -t dev&lt;/code&gt; and look for schema resources. Then decide which side of the boundary you want them on.&lt;/p&gt;

</description>
      <category>databricks</category>
      <category>dataengineering</category>
      <category>cloud</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Databricks Architecture Icons</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Mon, 10 Aug 2026 23:28:33 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/databricks-architecture-icons-2e9l</link>
      <guid>https://dev.to/oieduardorabelo/databricks-architecture-icons-2e9l</guid>
      <description>&lt;p&gt;If you have ever tried to draw an architecture diagram that includes Databricks products, you know the drill.&lt;/p&gt;

&lt;p&gt;You open the Databricks website, you right-click a hero image, you get a PNG at the wrong size, you give up and use a generic database cylinder.&lt;/p&gt;

&lt;p&gt;The official artwork exists, it just is not packaged for the tools people actually diagram in.&lt;/p&gt;

&lt;p&gt;So I packaged it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://oieduardorabelo.github.io/databricks-architecture-icons/" rel="noopener noreferrer"&gt;https://oieduardorabelo.github.io/databricks-architecture-icons/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpw519qbh87dy48uoq7j2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpw519qbh87dy48uoq7j2.png" alt="The set covers 71 products across 8 categories. Every icon is an official Databricks SVG file. Nothing is redrawn or traced." width="800" height="1331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;Databricks Architecture Icons is a community set of 71 products across 8 categories, ready for Mermaid, draw.io, Miro, Lucidchart, Excalidraw, Figma, Keynote and PowerPoint.&lt;/p&gt;

&lt;p&gt;Every icon is an official Databricks SVG, nothing is redrawn or traced. A build script pulls each source file, fits it into a 48 by 48 canvas, and produces five variants of each product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The plain vector&lt;/li&gt;
&lt;li&gt;A mono version that follows the theme colour&lt;/li&gt;
&lt;li&gt;A tile with a white glyph on the category colour&lt;/li&gt;
&lt;li&gt;An outline in a white box with a lava hairline, so it works on both light and dark pages&lt;/li&gt;
&lt;li&gt;A 256 pixel PNG for tools that reject SVG&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is unofficial. Databricks does not sponsor or endorse it. The artwork stays theirs.&lt;/p&gt;

</description>
      <category>databricks</category>
      <category>designsystem</category>
      <category>architecture</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Por que eu não uso bibliotecas de gerenciamento de estado no React</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Sat, 14 Dec 2024 01:18:44 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/por-que-eu-nao-uso-bibliotecas-de-gerenciamento-de-estado-no-react-1i</link>
      <guid>https://dev.to/oieduardorabelo/por-que-eu-nao-uso-bibliotecas-de-gerenciamento-de-estado-no-react-1i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Original source in English by&lt;/strong&gt; &lt;a href="https://x.com/ipla03" rel="noopener noreferrer"&gt;&lt;strong&gt;Fabrizio Beccaceci&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Why i no longer use a React state management library&lt;br&gt;
&lt;a href="https://medium.com/@ipla/why-i-no-longer-use-a-react-state-management-library-7bdffae54600" rel="noopener noreferrer"&gt;https://medium.com/@ipla/why-i-no-longer-use-a-react-state-management-library-7bdffae54600&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Índice
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;O que é uma Biblioteca de Gerenciamento de Estado Global?&lt;/li&gt;
&lt;li&gt;Como o Redux Toolkit Resolve o Estado Global&lt;/li&gt;
&lt;li&gt;As Duas Faces do Estado Global&lt;/li&gt;
&lt;li&gt;A Abordagem Enxuta para Estado Global

&lt;ul&gt;
&lt;li&gt;Para Estado do Servidor use TanStack Query&lt;/li&gt;
&lt;li&gt;Para Estado Compartilhado use Observables&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Por que não usar React Context?&lt;/li&gt;

&lt;li&gt;Conclusão&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Quando comecei a aprender React anos atrs, havia &lt;strong&gt;duas coisas que voc precisava saber&lt;/strong&gt; para se considerar um desenvolvedor React: &lt;strong&gt;TypeScript e Redux&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hoje em dia, as coisas mudaram. Se voc est aprendendo React agora e se depara com o conceito de gerenciamento de estado, vai se sentir sobrecarregado com uma infinidade de bibliotecas: Redux, Redux Toolkit, MobX, Jotai, Zustand e a lista continua.&lt;/p&gt;

&lt;p&gt;Mas antes de nos aprofundarmos, vamos responder pergunta bsica:&lt;/p&gt;

&lt;h2&gt;
  
  
  O que uma Biblioteca de Gerenciamento de Estado Global?
&lt;/h2&gt;

&lt;p&gt;Mesmo se voc for relativamente novo no React, provavelmente sabe o que o termo &lt;strong&gt;&lt;em&gt;estado&lt;/em&gt;&lt;/strong&gt;. Voc cria um com o hook &lt;code&gt;useState&lt;/code&gt;, e quando voc o atualiza, seu aplicativo renderiza novamente para refletir as mudanas.&lt;/p&gt;

&lt;p&gt;No entanto, &lt;strong&gt;as coisas ficam complicadas quando voc precisa compartilhar alguma informao entre vrias partes no conectadas do seu aplicativo&lt;/strong&gt; React.&lt;/p&gt;

&lt;p&gt;Isso o que frequentemente chamamos de &lt;strong&gt;estado global&lt;/strong&gt; , e o problema que as bibliotecas de gerenciamento de estado tentam resolver: &lt;strong&gt;como tornar o estado compartilhado acessvel a qualquer componente&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vamos ver como o Redux Toolkit lida com isso.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Como o Redux Toolkit Resolve o Estado Global&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Primeiro, voc cria uma &lt;strong&gt;&lt;em&gt;store&lt;/em&gt;&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;configureStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@reduxjs/toolkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;configureStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Inferindo os tipos de RootState e AppDispatch usando a store&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RootState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppDispatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este o &lt;strong&gt;local central&lt;/strong&gt; onde seu estado global ir existir. Em seguida, voc envolve seu aplicativo com um &lt;strong&gt;&lt;em&gt;provider&lt;/em&gt;&lt;/strong&gt; para tornar a &lt;em&gt;store&lt;/em&gt; acessvel a todos os componentes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-redux&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app/store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Provider&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;,
&lt;/span&gt;  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora, voc precisa definir os &lt;strong&gt;&lt;em&gt;slices&lt;/em&gt;&lt;/strong&gt; , que so partes individuais do seu estado global. Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createSlice&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@reduxjs/toolkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counterSlice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSlice&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reducers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;incrementByAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;incrementByAmount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;counterSlice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;counterSlice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por fim, voc conecta este &lt;em&gt;slice&lt;/em&gt;&lt;em&gt;store&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;configureStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@reduxjs/toolkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;counterReducer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./features/counter/counterSlice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;configureStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;counterReducer&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neste ponto, voc pode usar seu estado global nos componentes da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSelector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useDispatch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-redux&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;decrement&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./counterSlice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSelector&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useDispatch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;())}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Increment&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;())}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Decrement&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Embora o Redux Toolkit simplifique significativamente o Redux, ainda tem &lt;strong&gt;muito cdigo &lt;em&gt;boilerplate&lt;/em&gt; para o que geralmente uma necessidade simples&lt;/strong&gt;. E nem sequer falamos sobre aes assncronas como requisies ao servidor!&lt;/p&gt;

&lt;p&gt;Alm disso, muitos iniciantes caem na armadilha de colocar todo o seu estado no Redux, levando a stores globais infladas que so mais difceis de gerenciar.&lt;/p&gt;

&lt;h2&gt;
  
  
  As Duas Faces do Estado Global
&lt;/h2&gt;

&lt;p&gt;Quando voc para pra pensar, a maioria do "estado global" se enquadra em duas categorias:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Estado do Servidor&lt;/strong&gt; : Dados obtidos de um servidor, como uma lista de clientes em um aplicativo CRM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Estado da UI Compartilhado&lt;/strong&gt; : Pequenos pedaos de dados necessrios em vrios lugares, como o usurio atualmente logado.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Vamos abordar cada um deles separadamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;A Abordagem Enxuta para Estado Global&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Com ferramentas modernas, voc pode lidar com estado global sem uma biblioteca dedicada de gerenciamento de estado:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Para Estado do Servidor use TanStack Query&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TanStack Query (anteriormente React Query) feito especificamente para gerenciar estado do servidor. Ele lida com cache, recarregamento, dados obsoletos e muito mais, tudo pronto para uso, e voc definitivamente deveria usar isso porque voc realmente no vai querer reimplementar tudo isso do zero. Por exemplo, buscar uma lista de clientes to simples quanto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Customers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;fetchCustomers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;}&amp;lt;/u&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O TanStack Query elimina a necessidade de gerenciar manualmente o estado do servidor e, como o estado que voc busca armazenado em cache, voc pode us-lo exatamente da mesma maneira em todos os componentes e ele ser buscado apenas uma vez.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Para Estado Compartilhado use Observables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Para estados menores e especficos do aplicativo, como o usurio logado, eu uso uma implementao leve do padro &lt;strong&gt;Observable&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="na"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;subscribers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com isso, voc pode conectar Observables ao React usando &lt;code&gt;useSyncExternalStore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSyncExternalStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useObservable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useSyncExternalStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora, voc pode criar e usar &lt;strong&gt;&lt;em&gt;observables&lt;/em&gt;&lt;/strong&gt; desta forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loggedUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;loggedUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="c1"&gt;// Para acessar o valor dentro do Observable fora do React&lt;/span&gt;

&lt;span class="nx"&gt;loggedUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;someOtherUser&lt;/span&gt; &lt;span class="c1"&gt;// Para definir o valor, tanto fora quanto dentro do React&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dentro de um componente React:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useObservable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loggedUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Por que no usar React Context?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Embora o React Context funcione em alguns casos, ele tem desvantagens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Escopo Limitado&lt;/strong&gt; : Voc no pode acessar o Context fora dos componentes React.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Problemas de Performance&lt;/strong&gt; : Context dispara uma nova renderizao para todos os componentes abaixo dele, at mesmo para os componentes que no usem o valor atualizado.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Usando &lt;strong&gt;&lt;em&gt;Observables&lt;/em&gt;&lt;/strong&gt; evitamos ambos problemas!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Concluso&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Gerenciamento de estado global no precisa ser complicado. Combinando TanStack Query e um padro Observable leve, voc pode simplificar seu aplicativo evitando as armadilhas das bibliotecas tradicionais de gerenciamento de estado.&lt;/p&gt;

&lt;p&gt;Entre em contato comigo se quiser discutir sobre React, React Native, Nextjs&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Me contate no &lt;a href="https://x.com/ipla03" rel="noopener noreferrer"&gt;X (antigo Twitter)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ou acesse &lt;a href="https://www.iplastudio.com/" rel="noopener noreferrer"&gt;Iplastudio&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bom desenvolvimento! 🎉&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>redux</category>
      <category>ptbr</category>
    </item>
    <item>
      <title>S3 virus scanning with TypeScript and Node.js 20.x AWS Lambda Container</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Wed, 06 Dec 2023 05:06:45 +0000</pubDate>
      <link>https://dev.to/aws-builders/s3-virus-scanning-with-typescript-and-nodejs-20x-aws-lambda-container-220f</link>
      <guid>https://dev.to/aws-builders/s3-virus-scanning-with-typescript-and-nodejs-20x-aws-lambda-container-220f</guid>
      <description>&lt;p&gt;Searching the internet, you can find guides showing how to create a serverless virus scanning with ClamAV:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sutt0n/using-a-lambda-container-to-scan-files-with-clamav-via-serverless-2a5g"&gt;Using Serverless to Scan Files with ClamAV in a Lambda Container&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sutt0n/scanning-files-on-lambda-with-a-clamav-lambda-layer-475c"&gt;Using Serverless to Scan Files with a ClamAV Lambda Layer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tsh.io/blog/serverless-tutorial-virus-scanning-solution/" rel="noopener noreferrer"&gt;Serverless tutorial: Let’s build a virus scanning solution with automated database updates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even in the AWS Blog you can find good ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/developer/virus-scan-s3-buckets-with-a-serverless-clamav-based-cdk-construct/" rel="noopener noreferrer"&gt;Virus scan S3 buckets with a serverless ClamAV based CDK construct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws-samples/aws-serverless-s3-antivirus" rel="noopener noreferrer"&gt;AWS SAM application to keep your S3 objects safe from viruses using ClamAV Open Source software&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, a few aspects fell short of my expectations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1) They are not using the &lt;a href="https://www.clamav.net/downloads" rel="noopener noreferrer"&gt;latest version of ClamAV&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The examples above are installing ClamAV directly from the OS package registry (e.g., &lt;code&gt;yum install clamav&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;They aren't always in sync with the latest release, and if you don't update or upgrade the OS package registry, you will install older versions.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;yum install clamav&lt;/code&gt; for Amazon Linux 2, we receive the &lt;code&gt;0.100.x&lt;/code&gt; version, whereas the ClamAV release is already in the &lt;code&gt;1.x&lt;/code&gt; version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) Outdated Node.js runtime&lt;/strong&gt;&lt;br&gt;
Examples using Node.js use &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" rel="noopener noreferrer"&gt;the outdated Node.js 14.x runtime&lt;/a&gt;. This runtime is already under &lt;strong&gt;Deprecation (Phase 1)&lt;/strong&gt; in the AWS timeline for Node.js supported runtimes and &lt;a href="https://nodejs.org/en/about/previous-releases" rel="noopener noreferrer"&gt;is not part of the maintenance window of Node.js releases&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using outdated and unsupported versions is a risk I try to avoid!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3) Using plain JavaScript&lt;/strong&gt;&lt;br&gt;
While not a deal-breaker, I don't use plain JavaScript in my production projects.&lt;/p&gt;

&lt;p&gt;How would a TypeScript example look in the latest AWS Lambda runtime for Node.js?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4) How to use top-level await in Node.js handlers&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://dev.to/oieduardorabelo/top-level-await-in-aws-lamba-with-typescript-1bf0"&gt;Top-level await&lt;/a&gt; support in AWS Lambda is not new, but how can we configure it with everything else? (e.g., TypeScript, esbuild, etc). How can we output ESM code in the lambda handler?&lt;/p&gt;
&lt;h2&gt;
  
  
  Here it comes Amazon Linux 2023
&lt;/h2&gt;

&lt;p&gt;Starting on Node.js 20.x runtime, the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" rel="noopener noreferrer"&gt;default operational system for the AWS Lambda base image&lt;/a&gt; is Amazon Linux 2023.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/linux/al2023/ug/package-management.html" rel="noopener noreferrer"&gt;AL2023&lt;/a&gt; brings a new package management tool called &lt;code&gt;dnf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dnf&lt;/code&gt; is the successor to &lt;code&gt;yum&lt;/code&gt;, the package management tool in Amazon Linux 2.&lt;/p&gt;

&lt;p&gt;While many of the commands are compatible, for example, for the following Amazon Linux 2 &lt;code&gt;yum&lt;/code&gt; commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;packagename
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum search packagename
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum remove packagename
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In AL2023, they become these commands:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo dnf install packagename
$ sudo dnf search packagename
$ sudo dnf remove packagename
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Not everything stays the same. Be aware of changes! 🚨&lt;/p&gt;

&lt;p&gt;You can check the page &lt;a href="https://dnf.readthedocs.io/en/latest/cli_vs_yum.html" rel="noopener noreferrer"&gt;changes in &lt;code&gt;dnf&lt;/code&gt; CLI compared to &lt;code&gt;yum&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Let's build a newer example
&lt;/h2&gt;

&lt;p&gt;We'll keep the example project similar to the guides listed at the beginning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A S3 bucket with object notification to an AWS Lambda&lt;/li&gt;
&lt;li&gt;When an object is created in S3, a notification triggers the AWS Lambda&lt;/li&gt;
&lt;li&gt;The Lambda will read the file from the bucket, write it to &lt;code&gt;/tmp&lt;/code&gt;, and run &lt;code&gt;clamscan&lt;/code&gt; on it&lt;/li&gt;
&lt;li&gt;The returned code from &lt;code&gt;clamscan&lt;/code&gt; will be used to check the file status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fkzptasm0fd6emwq9v9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fkzptasm0fd6emwq9v9.png" alt="Diagram showing the example project: a s3 bucket with object created notification to lambda"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a few constraints I want to define for our newer example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because we need to download the ClamAV virus database in our lambda source code, the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html" rel="noopener noreferrer"&gt;uncompressed file size of 250MB&lt;/a&gt; can be an issue.&lt;/li&gt;
&lt;li&gt;We will be using &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/images-create.html" rel="noopener noreferrer"&gt;AWS Lambda Container&lt;/a&gt; image code, which enables us to have up to 10GB of uncompressed image size.&lt;/li&gt;
&lt;li&gt;The Docker &lt;code&gt;build&lt;/code&gt; process should take care of transpiling TypeScript to JavaScript and installing production-only dependencies.&lt;/li&gt;
&lt;li&gt;We want to download and install the latest ClamAV during the Docker &lt;code&gt;build&lt;/code&gt; process and update its virus definitions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The Dockerfile
&lt;/h3&gt;

&lt;p&gt;We can use &lt;a href="https://docs.docker.com/build/building/multi-stage/" rel="noopener noreferrer"&gt;Docker multi-stage builds&lt;/a&gt; to create these steps:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ========================================&lt;/span&gt;
&lt;span class="c"&gt;# Builder Image&lt;/span&gt;
&lt;span class="c"&gt;# ========================================&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;--platform=linux/x86_64 public.ecr.aws/lambda/nodejs:20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json index.ts  ./&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# 1) install dependencies with dev dependencies&lt;/span&gt;
&lt;span class="c"&gt;# 2) build the project&lt;/span&gt;
&lt;span class="c"&gt;# 3) remove dev dependencies&lt;/span&gt;
&lt;span class="c"&gt;# 4) install dependencies without dev dependencies&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; node_modules &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev

&lt;span class="c"&gt;# ========================================&lt;/span&gt;
&lt;span class="c"&gt;# Runtime Image&lt;/span&gt;
&lt;span class="c"&gt;# ========================================&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;--platform=linux/x86_64 public.ecr.aws/lambda/nodejs:20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; CLAMAV_PKG=clamav-1.2.1.linux.x86_64.rpm&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;
    set -ex

    &lt;span class="c"&gt;#&lt;/span&gt;
    &lt;span class="c"&gt;# install glibc-langpack-en to support english language and utf-8&lt;/span&gt;
    &lt;span class="c"&gt;# this was required by clamscan to avoid error "WARNING: Failed to set locale"&lt;/span&gt;
    &lt;span class="c"&gt;#&lt;/span&gt;
    dnf install wget glibc-langpack-en -y

    &lt;span class="c"&gt;# &lt;/span&gt;
    &lt;span class="c"&gt;# 1) download latest ClamAV from https://www.clamav.net/downloads&lt;/span&gt;
    &lt;span class="c"&gt;# 2) install using `rpm` and it requires full path for local packages&lt;/span&gt;
    &lt;span class="c"&gt;# 3) remove the downloaded package and clean up for smaller runtime image&lt;/span&gt;
    &lt;span class="c"&gt;# &lt;/span&gt;
    wget https://www.clamav.net/downloads/production/${CLAMAV_PKG}
    rpm -ivh "${LAMBDA_TASK_ROOT}/${CLAMAV_PKG}"
    rm -rf ${CLAMAV_PKG}
    dnf remove wget -y
    dnf clean all

    &lt;span class="c"&gt;#&lt;/span&gt;
    &lt;span class="c"&gt;# the current working directory is "/var/task" as defined in the base image:&lt;/span&gt;
    &lt;span class="c"&gt;# https://github.com/aws/aws-lambda-base-images/blob/nodejs20.x/Dockerfile.nodejs20.x&lt;/span&gt;
    &lt;span class="c"&gt;#&lt;/span&gt;
    &lt;span class="c"&gt;# 1) "lib/database" is the path to download the virus database&lt;/span&gt;
    &lt;span class="c"&gt;# 2) "freshclam.download.log" and "freshclam.conf.log" are the log files for freshclam CLI&lt;/span&gt;
    &lt;span class="c"&gt;#&lt;/span&gt;
    mkdir -p ${LAMBDA_TASK_ROOT}/lib/database
    touch ${LAMBDA_TASK_ROOT}/lib/{freshclam.download.log,freshclam.conf.log}
    chmod -R 777 ${LAMBDA_TASK_ROOT}/lib

    &lt;span class="c"&gt;#&lt;/span&gt;
    &lt;span class="c"&gt;# default configuration path for freshclam is "/usr/local/etc/freshclam.conf"&lt;/span&gt;
    &lt;span class="c"&gt;# we create a symbolic link to the default configuration path and copy our custom config file&lt;/span&gt;
    &lt;span class="c"&gt;#&lt;/span&gt;
    ln -s /usr/local/etc/freshclam.conf ${LAMBDA_TASK_ROOT}/lib/freshclam.conf
EOF

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; freshclam.conf /var/task/lib/freshclam.conf&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# freshclam CLI is a virus database update tool for ClamAV, documentation:&lt;/span&gt;
&lt;span class="c"&gt;# https://linux.die.net/man/1/freshclam&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;
    set -ex
    export LOG_FILE_PATH="${LAMBDA_TASK_ROOT}/lib/freshclam.conf.log"

    freshclam --verbose --stdout --user root \
        --log=${LOG_FILE_PATH} \
        --datadir=${LAMBDA_TASK_ROOT}/lib/database

    if grep -q "Can't download daily.cvd\|Can't download main.cvd\|Can't download bytecode.cvd" ${LOG_FILE_PATH}; then
        echo "ERROR: Unable to download ClamAV database files - your request may be being rate limited"
        exit 1;
    fi
EOF

&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# copy application files from the builder image&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /var/task/dist/* /var/task/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /var/task/node_modules /var/task/node_modules&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "index.handler" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The above Dockerfile covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install and build the TypeScript Lambda to JavaScript with production dependencies using a multi-stage build. The first stage &lt;code&gt;as builder&lt;/code&gt; creates the &lt;code&gt;dist&lt;/code&gt; folder and &lt;code&gt;node_modules&lt;/code&gt; folder used by the &lt;code&gt;as runtime&lt;/code&gt; stage&lt;/li&gt;
&lt;li&gt;Download the latest ClamAV from their release page, install it using &lt;code&gt;rpm&lt;/code&gt; and remove cache for smaller final image&lt;/li&gt;
&lt;li&gt;Download the ClamAV virus database definitions with &lt;code&gt;freshclam&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You can change the &lt;code&gt;CLAMAV_PKG&lt;/code&gt; to be in sync with the latest version of ClamAV&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚨 &lt;strong&gt;Important:&lt;/strong&gt; To update your database definition, you need to re-build this image every once in a while&lt;/p&gt;

&lt;p&gt;The required &lt;code&gt;freshclam.conf&lt;/code&gt; file contains the following:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CompressLocalDatabase yes
DatabaseDirectory /var/task/lib/database
DatabaseMirror database.clamav.net
DNSDatabaseInfo current.cvd.clamav.net
ScriptedUpdates no
UpdateLogFile  /var/task/lib/freshclam.conf.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;🚨 &lt;strong&gt;Important:&lt;/strong&gt; The full path files (e.g., &lt;code&gt;/var/task/*&lt;/code&gt; must match the Dockerfile definitions&lt;/p&gt;
&lt;h3&gt;
  
  
  The TypeScript AWS Lambda Handler
&lt;/h3&gt;

&lt;p&gt;For a S3 notification event, we can write our handler similar to:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;S3CreateEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GetObjectCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;S3Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-s3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;spawnSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writeFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3Client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// directories for clamscan&lt;/span&gt;
&lt;span class="c1"&gt;// "/tmp/files_to_scan" where we will store the files from s3 to scan&lt;/span&gt;
&lt;span class="c1"&gt;// "/tmp/clamscan_tmp" required by clamscan to store temporary files during the virus scan&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tmp/files_to_scan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tmp/clamscan_tmp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S3CreateEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;objectKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getObjectCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetObjectCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;objectKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3Object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getObjectCommand&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3ObjectContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;transformToString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tmpFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/tmp/files_to_scan/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;objectKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s3ObjectContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// clamscan CLI documentation:&lt;/span&gt;
    &lt;span class="c1"&gt;// https://linux.die.net/man/1/clamscan&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clamavScan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawnSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clamscan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--verbose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--stdout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`--database=/var/task/lib/database`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`--tempdir=/tmp/clamscan_tmp`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tmpFilePath&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clamavScan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// You can find the return codes here:&lt;/span&gt;
    &lt;span class="c1"&gt;// https://linux.die.net/man/1/clamscan&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clamavScan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no virus found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clamavScan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;virus found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clamavScan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;some error(s) occured in clamscan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpFilePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We use the top-level await feature and create two folders when the lambda container starts.&lt;/p&gt;

&lt;p&gt;Later, we use &lt;code&gt;spawnSync&lt;/code&gt; to trigger the &lt;code&gt;clamscan&lt;/code&gt; binary installed via the Dockerfile. &lt;/p&gt;

&lt;p&gt;Ensure you use full path definitions in the &lt;code&gt;clamscan&lt;/code&gt; parameters, for example: &lt;code&gt;/var/task/lib/database&lt;/code&gt;, to load the correct virus definitions.&lt;/p&gt;

&lt;p&gt;We can test the ClamAV detection using any &lt;a href="https://www.eicar.org/download-anti-malware-testfile/" rel="noopener noreferrer"&gt;EICAR text files&lt;/a&gt;. The result should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wkopxa24ofv9d0855l3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wkopxa24ofv9d0855l3.png" alt="Result of lambda handler execution of clamscan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we have our Dockerfile, ClamAV configuration, and Lambda handler. &lt;/p&gt;

&lt;p&gt;Where do we deploy all of that?&lt;/p&gt;
&lt;h2&gt;
  
  
  The CDK TypeScript Project
&lt;/h2&gt;

&lt;p&gt;Because Docker is building our lambda handler, we create its own &lt;code&gt;package.json&lt;/code&gt; with dependencies:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"clamav-scan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rimraf dist &amp;amp;&amp;amp; esbuild index.ts --format=esm --outfile=dist/index.mjs"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@types/aws-lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^8.10.130"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esbuild"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.19.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rimraf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.0.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"typescript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.3.2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws-sdk/client-s3"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.465.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Using &lt;code&gt;"type": "module"&lt;/code&gt; will tell TypeScript and Node.js that we are aiming to use ECMAScript Modules in our source code (ESM).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;build&lt;/code&gt; command asks &lt;code&gt;esbuild&lt;/code&gt; to output our source code in the ESM format with the &lt;code&gt;--format=esm&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;The last piece of the puzzle, is the &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"forceConsistentCasingInFileNames"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isolatedModules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NodeNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NodeNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noEmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"preserveConstEnums"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sourceMap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Using &lt;code&gt;NodeNext&lt;/code&gt; for &lt;code&gt;moduleResolution&lt;/code&gt; / &lt;code&gt;module&lt;/code&gt; and &lt;code&gt;ESNext&lt;/code&gt; for &lt;code&gt;target&lt;/code&gt;, will tell the TypeScript engine &lt;code&gt;tsc&lt;/code&gt; to output code in ESM format.&lt;/p&gt;

&lt;p&gt;The complete example can be found on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/oieduardorabelo" rel="noopener noreferrer"&gt;
        oieduardorabelo
      &lt;/a&gt; / &lt;a href="https://github.com/oieduardorabelo/s3-virus-scanning-typescript-aws-lambda-container" rel="noopener noreferrer"&gt;
        s3-virus-scanning-typescript-aws-lambda-container
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      S3 virus scanning with TypeScript and Node.js 20.x AWS Lambda Container
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ClamAV 1.2.1 with AWS Lambda Container Images for Node.js 20.x&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;CDK project for deploying a ClamAV 1.2.1 with AWS Lambda Container Images for Node.js 20.x&lt;/p&gt;

&lt;p&gt;This helps you to scan files for viruses using AWS Lambda functions&lt;/p&gt;

&lt;p&gt;🚨 Important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Virus definitions are updated during build&lt;/li&gt;
&lt;li&gt;Ensure you are building the container regularly to keep your definitions up to date&lt;/li&gt;
&lt;li&gt;You can update the Dockerfile to use a different version of ClamAV&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/ce240c1159d9fdcff2fc81d709f1be9917867fedd11ef3e9c78cceee6c8570db/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f70726163746963616c6465762f696d6167652f66657463682f732d2d5845735368796d5a2d2d2f635f6c696d6974253243665f6175746f253243666c5f70726f6772657373697665253243715f6175746f253243775f3830302f68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f34666b7a707461736d30666436656d77713976392e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/ce240c1159d9fdcff2fc81d709f1be9917867fedd11ef3e9c78cceee6c8570db/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f70726163746963616c6465762f696d6167652f66657463682f732d2d5845735368796d5a2d2d2f635f6c696d6974253243665f6175746f253243666c5f70726f6772657373697665253243715f6175746f253243775f3830302f68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f34666b7a707461736d30666436656d77713976392e706e67" alt="Diagram showing the example project: a s3 bucket with object created notification to lambda"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/oieduardorabelo/s3-virus-scanning-typescript-aws-lambda-container" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  🚨 WARNING: You are being rate-limited
&lt;/h2&gt;

&lt;p&gt;This is super important and caught me off guard multiple times. &lt;/p&gt;

&lt;p&gt;Pay attention to the number of viruses your database is using:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyom22lg405g74d8qqic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyom22lg405g74d8qqic.png" alt="Viruses definitions in your build"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During the build process of your Docker image, the ClamAV database mirror can rate limit your IP address and block you from downloading the virus definitions.&lt;/p&gt;

&lt;p&gt;For example, visiting to &lt;a href="https://database.clamav.net/main.cvd" rel="noopener noreferrer"&gt;https://database.clamav.net/main.cvd&lt;/a&gt;, can return the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnzv6ppsvjyxi1v7x7vq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnzv6ppsvjyxi1v7x7vq.png" alt="Rate limit main.cvd download"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ensure your &lt;code&gt;freshclam&lt;/code&gt; is downloading and loading the definitions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;daily.cvd&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhm77rbtxnw7i2rcp6ur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhm77rbtxnw7i2rcp6ur.png" alt="daily.cvd"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;main.cvd&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd12dk276p1r9ov8421a4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd12dk276p1r9ov8421a4.png" alt="main.cvd"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and &lt;strong&gt;bytecode.cvd&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk20s9fdmbx3uepjpmhdo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk20s9fdmbx3uepjpmhdo.png" alt="bytecode.cvd"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default &lt;code&gt;freshclam&lt;/code&gt; CLI will NOT throw an error when that happens.&lt;/p&gt;

&lt;p&gt;That's why in the Dockerfile we are grepping the log file generated by the CLI and looking for errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"Can't download daily.cvd&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;Can't download main.cvd&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;Can't download bytecode.cvd"&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_FILE_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: Unable to download ClamAV database files - your request may be being rate limited"&lt;/span&gt;
        &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we manually throw an error when any of the rate-limiting messages are detected! 🏁&lt;/p&gt;

</description>
      <category>aws</category>
      <category>node</category>
      <category>typescript</category>
      <category>docker</category>
    </item>
    <item>
      <title>Understanding AWS CloudFormation Execution Permissions</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Mon, 17 Jul 2023 09:51:12 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/understanding-aws-cloudformation-execution-permissions-1b8p</link>
      <guid>https://dev.to/oieduardorabelo/understanding-aws-cloudformation-execution-permissions-1b8p</guid>
      <description>&lt;p&gt;Do you know how CloudFormation generates permissions to provision resources in your account?&lt;/p&gt;

&lt;p&gt;In my new article on the AWS Fundamentals blog, we walk through the CloudFormation lifecycle and how you can apply security concepts like the least privileged access in the deployment of your stack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.awsfundamentals.com/aws-cloudformation-execution-permissions"&gt;https://blog.awsfundamentals.com/aws-cloudformation-execution-permissions&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>cloudformation</category>
    </item>
    <item>
      <title>AWS IAM Roles with AWS CloudFormation</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Sun, 09 Jul 2023 22:20:18 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/aws-iam-roles-with-aws-cloudformation-2kj5</link>
      <guid>https://dev.to/oieduardorabelo/aws-iam-roles-with-aws-cloudformation-2kj5</guid>
      <description>&lt;p&gt;If you are starting with CloudFormation and IAM Roles, I wrote an introductory article on the AWS Fundamentals blog:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.awsfundamentals.com/aws-iam-roles-with-aws-cloudformation"&gt;https://blog.awsfundamentals.com/aws-iam-roles-with-aws-cloudformation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you enjoyed this post &amp;amp; you want to get 𝗯𝗶𝘁𝗲-𝘀𝗶𝘇𝗲𝗱 𝗔𝗪𝗦 𝗰𝗼𝗻𝘁𝗲𝗻𝘁 straight to your inbox, feel free to check out our bi-weekly newsletter 📨&lt;/p&gt;

&lt;p&gt;You can also browse our 𝗽𝗿𝗲𝘃𝗶𝗼𝘂𝘀 𝗶𝘀𝘀𝘂𝗲𝘀 to see what you'll subscribe to 📚&lt;/p&gt;

&lt;p&gt;&lt;a href="https://newsletter.awsfundamentals.com/"&gt;https://newsletter.awsfundamentals.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>cloudformation</category>
    </item>
    <item>
      <title>Parte 03 - Maximize a economia de custos e escalabilidade do DynamoDB com índice secundário otimizado</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Wed, 12 Apr 2023 11:34:13 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/maximize-a-economia-de-custos-e-escalabilidade-do-dynamodb-com-indice-secundario-otimizado-5a4n</link>
      <guid>https://dev.to/oieduardorabelo/maximize-a-economia-de-custos-e-escalabilidade-do-dynamodb-com-indice-secundario-otimizado-5a4n</guid>
      <description>&lt;h1&gt;
  
  
  Créditos
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Escrito originalmente por &lt;a href="https://twitter.com/pj_naylor"&gt;Pete Naylor&lt;/a&gt;, em &lt;a href="https://www.gomomento.com/blog/maximize-cost-savings-and-scalability-with-an-optimized-dynamodb-secondary-index"&gt;Maximize cost savings and scalability with an optimized DynamoDB secondary index&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Entenda projeções e medição de dados. Esqueça a sobrecarga de GSI.
&lt;/h2&gt;

&lt;p&gt;Esta é a terceira parte de uma série de artigos sobre modelagem de dados do DynamoDB, criada principalmente para ajudar os desenvolvedores usando DynamoDB a entender as práticas recomendadas e evitar seguir um caminho lamentável com técnicas equivocadas de "design de tabela única". No episódio anterior - isso é um show agora? - introduzimos índices secundários locais e globais (LSIs e GSIs). &lt;/p&gt;

&lt;p&gt;Hoje, vamos nos aprofundar para entender quais dados da tabela são projetados em um índice secundário e como isso contribui para o consumo de taxa de transferência. Também vamos separar uma das reviravoltas mais recentes no "design de tabela única" — sobrecarga de GSI — e explicar por que geralmente é uma péssima ideia sem nenhum lado positivo real. Preparado? Lá vamos nós!&lt;/p&gt;

&lt;h1&gt;
  
  
  Quais mudanças na tabela base são relevantes para projeção em um índice secundário?
&lt;/h1&gt;

&lt;p&gt;Ao definir um índice secundário, você pode escolher quais atributos serão copiados (projetados) das alterações de itens &lt;strong&gt;relevantes&lt;/strong&gt; na tabela base: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ALL&lt;/code&gt; — todos os atributos no item da tabela base serão copiados para o índice secundário&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;INCLUDE&lt;/code&gt; — somente a lista nomeada de atributos será copiada (e também os atributos de chave primária da tabela base e o índice secundário — estes são sempre incluídos)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KEYS_ONLY&lt;/code&gt; — somente os atributos de chave primária da tabela base e o índice secundário serão copiados &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É assim que a medição funciona para índices secundários do DynamoDB: cada gravação feita em sua tabela base será considerada para a projeção de dados em cada um de seus índices secundários. Se a alteração do item na tabela base for &lt;strong&gt;relevante&lt;/strong&gt; para o índice secundário, ela será projetada e haverá consumo da unidade de gravação - medido de acordo com o tamanho do item projetado. Haverá um custo de armazenamento para os dados projetados no índice secundário também. E lembre-se, conforme explicado no primeiro artigo desta série, &lt;code&gt;Query&lt;/code&gt; e &lt;code&gt;Scan&lt;/code&gt; (as únicas operações de leitura disponíveis para índices secundários) agregam o tamanho de todos os itens e, em seguida, arredondam para cima, para avaliar o total da medição da unidade de leitura. Se você mantiver a projeção de seus itens pequena, o índice custará menos e será dimensionado ainda mais antes de enfrentar qualquer tipo de preocupação importante. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Considere a projeção com muito cuidado para seu índice secundário. &lt;code&gt;ALL&lt;/code&gt; parece simples, mas pode ser muito caro e pode inibir a escalabilidade de seu design em geral. &lt;strong&gt;Projete apenas os atributos que você realmente precisa&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Há mais detalhes nesta equação de eficiência para índices secundários. Você provavelmente está se perguntando por que continuo enfatizando a palavra &lt;strong&gt;relevante&lt;/strong&gt; . Bem, é porque é realmente importante! Primeiro, como o DynamoDB oferece flexibilidade de esquema, os atributos definidos como a chave para seu índice secundário não precisam estar presentes em todos os itens de sua tabela (a menos que façam parte da chave primária dessa tabela). Se os atributos da chave do índice não estiverem presentes em um item da tabela, ele não será considerado &lt;strong&gt;relevante&lt;/strong&gt; para projeção no índice. Você pode usar isso para criar um "índice esparso" — um poderoso mecanismo de filtragem — para localizar os dados necessários com o menor custo de armazenamento e taxa de transferência.&lt;/p&gt;

&lt;p&gt;Um exemplo pode ser um banco de dados que armazena detalhes de status de milhões de tarefas - a grande maioria delas sendo concluída. Como podemos tornar as busca de tarefas que estão no status "enviado" mais fácil? Para tarefas que estão no status relevante da busca ("enviado"), crie um atributo que indique isso e adicione um índice secundário que seja definido por esse atributo. A presença do atributo efetivamente se torna um sinalizador - quando a tarefa for concluída, remova este atributo - a tarefa não estará mais visível no índice secundário quando a busca for realizada. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4SCBvaFE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4l7bvfbqec01ybtvbz9c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4SCBvaFE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4l7bvfbqec01ybtvbz9c.png" alt="GSI waiting-jobs-index" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;[Nossa tabela base que rastreia o status da tarefa]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c--VAA15--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1c4ytz9x5l58dawta260.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c--VAA15--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1c4ytz9x5l58dawta260.png" alt="GSI waiting-jobs-index-ALL" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;[Índice secundário esparso que contém apenas tarefas aguardando atribuição]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Agora vamos imaginar que, como parte do fluxo de trabalho para tarefas com status enviado, o item de tarefa na tabela seja atualizado cinco vezes para adicionar vários detalhes em atributos não-chave. Se esses atributos forem projetados para nosso índice secundário esparso de tarefas enviadas, o índice também precisará ser atualizado cinco vezes, consumindo várias unidades de gravação. Mas nosso índice secundário esparso realmente não tem utilidade para esses atributos - eles não são relevantes para o padrão de acesso que precisamos que o índice atenda. Portanto, escolhemos uma projeção &lt;code&gt;KEYS_ONLY&lt;/code&gt; para o índice — ela mantém baixo o consumo de unidade de leitura, maximiza a escalabilidade de busca de tarefas, economiza dinheiro em armazenamento e evita várias gravações desnecessárias na projeção do índice. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8TpAg4TG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gesd4qupjbtlofkib9mk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8TpAg4TG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gesd4qupjbtlofkib9mk.png" alt="GSI waiting-jobs-index-KEYS_ONLY" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;[O mesmo índice secundário, agora esparso com projeção KEYS_ONLY. Muito mais eficiente!]&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  O que é "sobrecarga de GSI" e por que é uma má ideia?
&lt;/h1&gt;

&lt;p&gt;Em 2017, as equipes da Amazon estavam trabalhando duro para &lt;a href="https://www.youtube.com/watch?v=qcuH2ikQkaM"&gt;migrar suas cargas de trabalho mais críticas&lt;/a&gt; de bancos de dados relacionais para o DynamoDB. Eles estavam aprendendo a pensar de maneira diferente sobre a modelagem de dados, olhando além da familiar terceira forma normal (3FN) e desnormalizando dados em itens e coleções de itens no DynamoDB. Naquela época, o DynamoDB suportava no máximo 5 GSIs por tabela, limite máximo. Em alguns casos raros, haveria uma equipe com uma tabela que exigia mais de 5 GSIs para cobrir todos os seus padrões de acesso. Para aqueles de nós que trabalham para dar suporte a essas equipes, percebemos que algumas das necessidades de índice podem ser esparsas e não se sobrepõem. Talvez com alguma adaptação instável pudéssemos usar o mesmo GSI para cobrir vários requisitos de padrão de acesso - isso poderia nos ajudar a trapacear no limite do GSI? Sim: era o último recurso, tinha implicações terríveis de eficiência e operabilidade - mas às vezes funcionava. Nós nos referimos a isso como "sobrecarga de GSI".&lt;/p&gt;

&lt;p&gt;Avançando para dezembro de 2018, o DynamoDB &lt;a href="https://aws.amazon.com/about-aws/whats-new/2018/12/amazon-dynamodb-increases-the-number-of-global-secondary-indexes-and-projected-index-attributes-you-can-create-per-table/"&gt;aumentou o limite de GSIs&lt;/a&gt; por tabela para 20. Alegria! Não há necessidade do hack feio de sobrecarga a partir de agora... certo? Bem, a equipe de engenharia pensou que sim, mas então surgiram algumas reviravoltas perturbadoras na fábula do "design de tabela única". &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Acontece que, se você fizer coisas não naturais, desnecessárias, complexas e ineficientes para enviar dados completamente não relacionados para apenas uma tabela do DynamoDB sem motivo, acabará tendo que criar mais índices secundários para essa tabela. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Se você estiver projetando dados com o objetivo de ter exatamente uma tabela (em vez de priorizar eficiência, flexibilidade e escalabilidade), é mais provável que você encontre o limite de GSIs por tabela - e então a mesma obsessão de "exatamente uma tabela" gere um desejo equivocado ter exatamente um GSI, o que leva à sobrecarga. Isso cria uma série de problemas. Na verdade, essa técnica não traz nenhum benefício — é uma má ideia. Aqui está o porquê: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Você provavelmente precisará fazer compensações para a projeção de dados que lhe custará dinheiro e prejudicará a escalabilidade do seu design. O menor denominador comum é projetar &lt;code&gt;ALL&lt;/code&gt; e padronizar strings para atributos de chave primária (quando números seriam mais eficientes). Ai!&lt;/li&gt;
&lt;li&gt;Você perde a flexibilidade de poder varrer o índice secundário e recuperar apenas os dados de seu interesse. Essa é uma funcionalidade poderosa que se foi - sem nenhum motivo.&lt;/li&gt;
&lt;li&gt;Uma das grandes vantagens dos GSIs é que você pode excluí-los e recriá-los conforme necessário. Excluir um GSI não custa nada. Se você misturou vários requisitos de índice em um único GSI e em algum momento percebeu que não precisa indexar um de seus padrões, você não pode simplesmente excluir esse GSI. Você terá que (cuidadosamente) voltar para o design da sua tabela e fazer atualizações em massa relativamente caras (e potencialmente arriscadas). Você realmente não quer lidar com isso. &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Lições importantes
&lt;/h1&gt;

&lt;p&gt;Resumindo, não há benefício na "sobrecarga de GSI" – apenas pontos negativos. Não faça isso! Além disso, se você se preocupa com a otimização de custos e a escalabilidade, considere a &lt;strong&gt;dispersão e a projeção mínima ao projetar índices secundários&lt;/strong&gt; no DynamoDB.&lt;/p&gt;

&lt;p&gt;No próximo artigo desta série, pretendo desenvolver o que aprendemos até agora para explicar onde as coisas deram errado com o "design de tabela única" e por que interpretá-lo exatamente como uma tabela é um erro terrível (que as equipes da Amazon não estão fazendo). &lt;/p&gt;




&lt;p&gt;Se você quiser discutir esse tópico comigo, obter minha opinião sobre uma pergunta de modelagem de dados do DynamoDB que você tem ou sugerir tópicos para eu escrever em artigos futuros, entre em contato comigo no Twitter (&lt;a href="https://twitter.com/pj_naylor"&gt;@pj_naylor&lt;/a&gt;) ou envie-me um [e-mail diretamente]mailto:&lt;a href="mailto:petenaylor@momentohq.com"&gt;petenaylor@momentohq.com&lt;/a&gt;)!&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>aws</category>
      <category>cloud</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>Parte 02 - Qual tipo de índice secundário do DynamoDB você deve escolher?</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Wed, 12 Apr 2023 11:21:47 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/qual-tipo-de-indice-secundario-do-dynamodb-voce-deve-escolher-3ieg</link>
      <guid>https://dev.to/oieduardorabelo/qual-tipo-de-indice-secundario-do-dynamodb-voce-deve-escolher-3ieg</guid>
      <description>&lt;h2&gt;
  
  
  Créditos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Escrito originalmente por &lt;a href="https://twitter.com/pj_naylor" rel="noopener noreferrer"&gt;Pete Naylor&lt;/a&gt;, em &lt;a href="https://www.gomomento.com/blog/which-flavor-of-dynamodb-secondary-index-should-you-pick" rel="noopener noreferrer"&gt;Which flavor of DynamoDB secondary index should you pick?&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Use o poder dos índices secundários locais e globais.
&lt;/h2&gt;

&lt;p&gt;Este é o segundo artigo de uma série curta sobre modelagem de dados no Amazon DynamoDB (sem os equívocos de "design de tabela única"). Se você não leu o primeiro artigo, eu o encorajo &lt;a href="https://www.gomomento.com/blog/what-really-matters-in-dynamodb-data-modeling" rel="noopener noreferrer"&gt;a dar uma olhada&lt;/a&gt;. Nele, discuto os conceitos mais importantes da modelagem do DynamoDB: &lt;strong&gt;flexibilidade de esquema&lt;/strong&gt; e &lt;strong&gt;coleções de itens&lt;/strong&gt;. Vou us essa base para discutir índices secundários, um dos meus recursos favoritos do DynamoDB. Observe que este artigo assume alguma familiaridade com os principais conceitos do DynamoDB, como partições, itens, tipos de chave primária e tipos de dados. Se você precisa se atualizar sobre eles, aqui está uma &lt;a href="https://www.youtube.com/playlist?list=PLJo-rJlep0EDNtcDeHDMqsXJcuKMcrC5F" rel="noopener noreferrer"&gt;lista de vídeos que eu recomendo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Índices secundários são poderosos! Você pode usá-los para fornecer automaticamente diferentes perspectivas para leituras dos dados em sua tabela. Eles ajudam você a definir e manter relacionamentos adicionais (coleções de itens) dentro dos mesmos dados, permitem que você classifique os dados relacionados com uma dimensão diferente e podem criar filtros incrivelmente eficazes.&lt;/p&gt;

&lt;p&gt;Mas, como qualquer outra ferramenta, é possível utilizar os índices secundários do DynamoDB de um modo ruim.&lt;/p&gt;

&lt;p&gt;Com grandes poderes vêm grandes responsabilidades - e uma grande necessidade de entender suas opções! Neste blog, vou me concentrar em comparar os diferentes tipos de índices secundários e oferecer algumas dicas para escolher entre eles. Deixarei de falar sobre escolhas de projeção de índice secundário e as consequências de custo e escalabilidade para o próximo artigo, além de algumas observações sobre uma tendência recente no uso de índices secundários globais do DynamoDB chamada "sobrecarga" (&lt;em&gt;overloading&lt;/em&gt;) e por que ela deve ser evitada no design das suas tabelas na maioria dos casos.&lt;/p&gt;

&lt;p&gt;Então fique ligado! E segurem seus chapéus - será uma turnê alucinante.&lt;/p&gt;

&lt;h2&gt;
  
  
  Onde vivem os índices secundários? E como eles chegam lá?
&lt;/h2&gt;

&lt;p&gt;Os dados de sua tabela (o índice primário) são projetados em cseus índies secundários pelo DynamoDB com base na definição de índice que você fornece. Você não pode escrever diretamente em um índice secundário, mas ao escrever em um item em sua tabela base, o DynamoDB projetará alterações relevantes em seus índices secundários para você. Existem dois tipos de índice secundário: índices secundários locais (LSIs - Local Secondary Indexes) e índices secundários globais (GSIs - Global Secondary Indexes).&lt;/p&gt;

&lt;h2&gt;
  
  
  Índices secundários locais
&lt;/h2&gt;

&lt;p&gt;Os LSIs residem nas mesmas partições do DynamoDB que a tabela base — eles compartilham o mesmo atributo de chave de partição (mas têm um atributo de chave de classificação diferente) e compartilham a taxa de transferência com a tabela base. Os LSIs são locais porque oferecem uma ordem de classificação diferente para uma coleção de itens dentro da mesma partição. Os LSIs oferecem suporte a &lt;strong&gt;leituras fortemente consistentes após escrever na tabela&lt;/strong&gt; base, se o parâmetro for especificado na hora da solicitação de dados (caso contrário, a consistência eventual padrão é usada).&lt;/p&gt;

&lt;p&gt;Na verdade, os LSIs e as tabelas base compartilham as mesmas coleções de itens — e isso restringe cada coleção de itens a residir em uma única partição. Quando uma tabela tem um ou mais LSIs, cada coleção de itens nunca pode crescer além de aproximadamente 10 GB (todos os dados para o mesmo valor da chave de partição na tabela base e todos os LSIs combinados). A taxa de transferência de leitura e escrita para qualquer coleção de itens é limitada a 3.000 unidades de leitura por segundo e 1.000 unidades de escrita por segundo na tabela e todos os LSIs associados.&lt;/p&gt;

&lt;p&gt;Os LSIs &lt;strong&gt;devem ser definidos quando a tabela é criada&lt;/strong&gt; e não podem ser excluídos sem excluir a tabela base associada. &lt;strong&gt;Pense bem antes de usar LSIs em seu modelo de dados&lt;/strong&gt; - você deve ter um bom motivo (como um requisito válido para leituras fortemente consistentes) e deve saber que nunca terá uma coleção de itens que possa crescer e exigir mais de 10 GB, 3.000 unidades de leitura por segundo ou 1.000 unidades de escrita por segundo.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Se você decidir posteriormente que não quer ser limitado pelas propriedades dos LSIs ou não precisa mais de um LSI específico, pode ser necessária uma migração complexa de sua existente tabela para uma tabela de substituição/nova tabela.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Índices secundários globais
&lt;/h2&gt;

&lt;p&gt;Os GSIs são como uma tabela separada — eles podem ter um atributo de chave de partição diferente e têm suas próprias partições e capacidade de taxa de transferência. Eles podem ser criados (com preenchimento/&lt;em&gt;with backfill&lt;/em&gt;) conforme necessário e removidos (sem custo) quando não forem mais necessários. Eles são globais porque permitem que novos relacionamentos (coleções de itens) sejam definidos entre itens em todas as partições da tabela base. As coleções de itens em um GSI podem abranger partições para armazenar mais dados e fornecer maior taxa de transferência (também verdadeiro para a tabela base, desde que não haja LSIs).&lt;/p&gt;

&lt;p&gt;Uma das maiores diferenças entre LSIs e GSIs está em seu comportamento durante a escrita na tabela base. Como a tabela base e quaisquer LSIs compartilham as mesmas partições, quaisquer atualizações nos LSIs são manipuladas atomicamente com a alteração no item da tabela base. Para GSIs, a &lt;strong&gt;alteração é propagada de forma assíncrona&lt;/strong&gt; para uma partição diferente. Isso tem implicações de consistência de leitura após escrita. As leituras de um LSI podem ser solicitadas para serem consistentes, se desejado, mas &lt;strong&gt;uma leitura de um GSI é sempre eventualmente consistente&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Uma consideração adicional para leituras de um GSI é a &lt;a href="https://blog.palantir.com/on-monotonicity-in-relational-databases-and-service-oriented-architecture-90b0a848dd3d" rel="noopener noreferrer"&gt;monotonicidade&lt;/a&gt;. As leituras GSI não são monotônicas. Se você atualizar um item em sua tabela base para incrementar o valor de um atributo de 7 para 8, poderá fazer três leituras sucessivas dos dados projetados em seu GSI e ver o valor como 8 primeiro, depois 7 e, finalmente, de volta para 8. Uma série de leituras para os mesmos dados em um GSI pode retornar resultados que avançam e retrocedem ao longo do tempo. As leituras fortemente consistentes da tabela ou de um LSI são monotônicas.&lt;/p&gt;

&lt;p&gt;Os GSIs são mais flexíveis que os LSIs, e qualquer LSI pode ser facilmente modelado como um GSI. Use LSIs apenas se tiver certeza de que o índice precisará suportar leituras consistentes/monotônicas ou se quiser se beneficiar do recurso de "leitura completa" (&lt;em&gt;read through&lt;/em&gt;) (detalhes sobre isso em um artigo futuro).&lt;/p&gt;

&lt;h2&gt;
  
  
  Propriedades interessantes de chaves de índice secundárias
&lt;/h2&gt;

&lt;p&gt;Em primeiro lugar: o valor da chave de índice não é garantido como único, como a chave primária em sua tabela base. O índice pode ter várias entradas projetadas com os mesmos valores para a chave de partição do índice e a chave de ordenação! A API GetItem não tem suporte para índices secundários porque GetItem implica a leitura de no máximo um item para um determinado valor da chave. Mas em um índice secundário, mesmo quando um valor específico da chave de índice é fornecido, você pode ver muitos itens retornados! Você deve usar Query ou Scan para ler de um LSI ou GSI. LSIs fornecem uma ordenação alternativa (&lt;em&gt;sort key&lt;/em&gt;), GSIs fornecem coleções alternativas e ordenação (opcional).&lt;/p&gt;

&lt;p&gt;Conforme mencionado anteriormente, um LSI deve ter uma chave composta. A tabela base à qual o LSI está anexado também deve ter uma chave composta. A chave de partição do LSI deve ser a mesma da tabela base e o atributo da chave de ordenação deve ser diferente daquele da tabela. Um exemplo simples pode ser uma interface do usuário que lista um conjunto de entradas em formato tabular — as entradas são agrupadas (coleções de itens relacionadas pelo mesmo valor do atributo chave de partição) e ordenadas por um dos valores de seus atributos (a chave de ordenação da tabela base). E se o usuário precisar ordenar o mesmo grupo de entradas por um atributo diferente? É aqui que um LSI pode ajudá-lo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F990c3y1clevgtgtfcze6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F990c3y1clevgtgtfcze6.gif" alt="Um modelo de dados de carrinho de compras usando LSIs para ordernar a lista com um atributo diferente."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GSIs são mais flexíveis: uma chave de partição é necessária, mas pode ser diferente da tabela base - você pode relacionar/agrupar os dados da sua tabela em um atributo diferente! Uma GSI pode ter uma chave simples ou uma chave composta — e o mesmo vale para a tabela base à qual está anexada. Se você não precisa recuperar coleções de itens de seu GSI ordenados (talvez o padrão seja apenas consultar todos os itens que têm um valor comum de chave de partição no índice), não defina uma chave de ordenação - a GSI ainda pode construir coleções de itens para uma busca de dados eficiente. &lt;strong&gt;Definir uma chave de ordenação quando não é necessário pode limitar sua escalabilidade para as coleções de itens.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Detalhando distinções entre tipos de índices secundários
&lt;/h2&gt;

&lt;p&gt;Há muitas nuâncias aqui, mas na verdade se resumem a algumas diferenças simples, então fiz uma folha de dicas que você pode usar na próxima vez que estiver considerando suas opções de índice secundário.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyc4o2ybp1nupk2o5j5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyc4o2ybp1nupk2o5j5t.png" alt="Tabela mostrando as nuâncias dos tipos de índices secundários do DynamoDB"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Fique de olho no próximo artigo desta série de modelagem de dados do DynamoDB (que visa detalhar o "design de tabela única").&lt;/p&gt;

&lt;p&gt;Em artigos futuros: mais sobre indexação secundária e uma discussão resumida sobre onde o "design de tabela única" deu terrivelmente errado.&lt;/p&gt;




&lt;p&gt;Se você quiser discutir esse tópico comigo, obter minha opinião sobre uma pergunta de modelagem de dados do DynamoDB que você tem ou sugerir tópicos para eu escrever em artigos futuros, entre em contato comigo no Twitter (&lt;a href="https://twitter.com/pj_naylor" rel="noopener noreferrer"&gt;@pj_naylor&lt;/a&gt;) ou envie-me um [e-mail diretamente]mailto:&lt;a href="mailto:petenaylor@momentohq.com"&gt;petenaylor@momentohq.com&lt;/a&gt;)!&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>aws</category>
      <category>cloud</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>Parte 01 - O que realmente importa na modelagem de dados do DynamoDB?</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Wed, 12 Apr 2023 11:07:54 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/o-que-realmente-importa-na-modelagem-de-dados-do-dynamodb-69d</link>
      <guid>https://dev.to/oieduardorabelo/o-que-realmente-importa-na-modelagem-de-dados-do-dynamodb-69d</guid>
      <description>&lt;h2&gt;
  
  
  Créditos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Escrito originalmente por &lt;a href="https://twitter.com/pj_naylor"&gt;Pete Naylor&lt;/a&gt;, em &lt;a href="https://www.gomomento.com/blog/what-really-matters-in-dynamodb-data-modeling"&gt;What really matters in DynamoDB data modeling?&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Spoiler: não é o número de tabelas!
&lt;/h2&gt;

&lt;p&gt;Quando deixei a equipe do DynamoDB alguns meses atrás, decidi que precisava compartilhar o que aprendi sobre como modelar dados para o DynamoDB e operá-lo bem. Durante &lt;strong&gt;meus seis anos trabalhando com clientes internos e externos do DynamoDB na AWS&lt;/strong&gt;, tive a sorte de obter exposição a uma ampla variedade de modelos de dados do DynamoDB. Eu também vi como eles evoluíram na operação: como eles foram dimensionados (ou não) utilização com carga variável, sua flexibilidade para acomodar novos requisitos de aplicativos e se eles se tornaram proibitivos em termos de custo ao longo do tempo.&lt;/p&gt;

&lt;p&gt;Esta é a primeira parte de uma pequena série de artigos que publicarei com o objetivo de corrigir alguns equívocos sobre as práticas recomendadas de modelagem de dados do DynamoDB. Nos últimos anos, recomendações estranhas evoluíram por meio dos rumores das mídias sociais (com a ajuda do marketing da AWS). Vou escrever sobre o mundo real. Darei os mesmos conselhos que a equipe do DynamoDB dá a outras equipes da Amazon para quem o serviço é de missão crítica - as mesmas recomendações de otimização, os mesmos avisos sobre o placebo do "design de tabela única", as mesmas dicas sobre a interpretação de métricas, os mesmos detalhes nas nuâncias de mensuração e medição, e o mesmo foco na excelência operacional.&lt;/p&gt;

&lt;p&gt;Pronto para começar? Nesse primeiro artigo, definirei as bases para a série, compartilhando o molho secreto para o sucesso com o DynamoDB. Reconheço que demorei um pouco para realmente grocar essas coisas. Me siga! Eu conheço alguns atalhos.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que realmente importa se não é tudo sobre o número de tabelas?
&lt;/h2&gt;

&lt;p&gt;Os dois primeiros conceitos que você precisa entender ao aprender modelagem de dados do DynamoDB para levar seu pensamento além dos casos de uso de chave-valor simples são:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Flexibilidade de Esquema&lt;/li&gt;
&lt;li&gt;Coleções de itens&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Flexibilidade de esquema significa que nem todos os itens em uma tabela do DynamoDB exigem a mesma estrutura – cada item pode ter seu próprio conjunto de atributos e tipos de dados – isso abre muitas possibilidades! Na verdade, os únicos atributos para os quais o esquema é obrigatório são os atributos de chave primária — aqueles usados ​​para indexar os dados na tabela. Cada item deve incluir os atributos-chave definidos para a tabela com o tipo de dados correto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uma coleção de itens é o conjunto de todos os itens que possuem o mesmo valor de atributo que a chave de partição&lt;/strong&gt;; em outras palavras, eles estão todos relacionados pelo valor da chave de partição. Se você estiver familiarizado com bancos de dados relacionais, uma maneira de pensar sobre isso é olhar para uma coleção de itens como sendo um pouco como um &lt;code&gt;JOIN&lt;/code&gt; materializado, onde o valor comum do atributo de chave de partição é algo como uma chave estrangeira. Para otimizar seu modelo de dados do DynamoDB, você deve procurar oportunidades para usar coleções de itens na tabela base ou em índices secundários, especialmente itens que se relacionam e que seriam buscados (seletivamente) juntos. Isso pode parecer óbvio, mas vale a pena deixar claro: &lt;strong&gt;para estar na mesma coleção de itens, os itens também precisam estar na mesma tabela – a coleção de itens é um subconjunto da tabela.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Por exemplo, você pode representar um carrinho de compras como uma coleção de itens, em que o identificador exclusivo do carrinho (Pete's Cart) é o valor da chave de partição e o identificador de cada produto adicionado ao carrinho é o valor da chave de ordenação (&lt;em&gt;sort&lt;/em&gt;). Aparentemente, Pete gosta mais de café do que de vegetais, mas não tanto quanto de chocolates. O número de cada tipo de produto no carrinho é outro atributo (não-chave). A imagem abaixo descreve tal coleção de itens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OznncuvX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yc7q608eytre9nh7yvv7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OznncuvX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yc7q608eytre9nh7yvv7.png" alt="Um carrinho de compras com itens." width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seu código tem uma definição mais rígida, como a captura de tela do NoSQL Workbench abaixo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X7MKfUU6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7emh6jey0rwjjt0cvgo1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X7MKfUU6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7emh6jey0rwjjt0cvgo1.png" alt="Tela no NoSQL Workbench mostrando a modelagem de dados para um carrinho de compras com Partition Key, Sort Key e Attributes" width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As coleções de itens podem existir no índice primário (a tabela base) e/ou em um índice secundário. Vou me aprofundar nos detalhes dos índices secundários em um artigo futuro, então vamos nos concentrar na tabela base. As coleções de itens na tabela base requerem uma chave primária composta (chave de partição e chave de ordenação). Se não houver índices secundários locais (LSIs), a coleção de itens pode abranger várias partições do DynamoDB — ela não começará dessa forma, mas o DynamoDB dividirá automaticamente a coleção de itens entre as partições para acomodar o crescente volume de dados e, às vezes, ele também pode distribuir a coleção de itens entre as partições para fornecer uma taxa de transferência generalizada ou para isolar itens mais quentes (ou intervalos de itens).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MT9SiRYA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qmernv4byvt4ceu6b7gy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MT9SiRYA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qmernv4byvt4ceu6b7gy.png" alt="Distribuição de caractéres em uma piscina de letras." width="800" height="917"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;É útil pensar em itens e coleções de itens como sendo dois tipos diferentes de registros no DynamoDB. Em uma tabela base com uma chave primária simples (sem chave de ordenação definida), você trabalha inteiramente com itens. Mas se sua tabela tiver uma chave primária composta (chave de partição + chave de ordenação), você trabalhará com coleções de itens. Os itens na coleção são armazenados na ordem dos valores da chave de ordenação e podem ser retornados na ordem descendente ou ascendente.&lt;/p&gt;

&lt;p&gt;Uma coleção de itens é mágica – ela nos permite armazenar e recuperar itens relacionados juntos de forma eficiente. Os itens na coleção podem ter esquemas diferentes (o DynamoDB é flexível, lembra?) — cada um representando uma parte do registro geral da coleção de itens.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Se você tiver um item de 200 KB, atualizar até mesmo uma pequena parte desse item consumirá 200 unidades de escrita. Se, ao invés disso, esse item for armazenado como uma coleção de partes de itens em uma coleção, você poderá atualizar qualquer pequena parte para um consumo mínimo de unidade de escrita. Ei, mas isso é só o começo!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Você pode usar a operação &lt;code&gt;Query&lt;/code&gt; para recuperar todos os itens da coleção ou apenas aqueles com um intervalo específico de valores de chave de ordenação. A especificação para o intervalo é chamada de &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions"&gt;condição de chave de ordenação (&lt;em&gt;sort key condition&lt;/em&gt;)&lt;/a&gt;. Dentro da coleção de itens, você pode limitar os itens recuperados para serem aqueles com uma chave de ordenação menor que um valor escolhido, ou maior que, ou mesmo entre dois valores.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E8H7fiRl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zzmmvn7m6ele0qk1g6gq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E8H7fiRl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zzmmvn7m6ele0qk1g6gq.png" alt="Ilustração de como o sort key condition funciona em uma coleção de itens." width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para chaves de ordenação em strings, você também pode usar &lt;code&gt;begin_with&lt;/code&gt; (que na verdade é apenas uma variação de &lt;code&gt;between&lt;/code&gt;, se você pensar bem). A aplicação de uma condição de chave de ordenação limita efetivamente o intervalo de itens a serem retornados da coleção de itens. É importante ressaltar que apenas os itens da coleção que correspondem à condição da chave de ordenação serão incluídos na medição das unidades de leitura (&lt;em&gt;read units&lt;/em&gt;). Isso contrasta com uma &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression"&gt;expressão de filtro&lt;/a&gt; que se aplica após a avaliação das unidades de leitura - portanto, &lt;strong&gt;você ainda paga para ler os itens filtrados&lt;/strong&gt;. Quando você recupera vários itens usando &lt;code&gt;Query&lt;/code&gt; (ou &lt;code&gt;Scan&lt;/code&gt;), a medição do consumo da unidade de leitura adiciona os tamanhos de todos os itens e &lt;em&gt;depois&lt;/em&gt; arredonda para o próximo limite de 4KB (ao invés de arredondar para cada item como &lt;code&gt;GetItem&lt;/code&gt; e &lt;code&gt;BatchGetItem&lt;/code&gt; fazem). Assim, as coleções de itens permitem que você &lt;a href="https://aws.amazon.com/blogs/database/use-vertical-partitioning-to-scale-data-efficiently-in-amazon-dynamodb/"&gt;armazene registros muito grandes, atualize partes seletivas a baixo custo e recupere-as com eficiência ideal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Coleções de itens (&lt;code&gt;Query&lt;/code&gt;) e &lt;code&gt;Scan&lt;/code&gt; são, na verdade, os únicos diferenciadores atraentes de custo e desempenho que defendem o armazenamento de dois itens na mesma tabela. Ambos permitem a recuperação de vários itens com os tamanhos de itens agregados arredondados para o próximo limite de 4 KB. Se você não for indexar dois itens juntos na mesma coleção de itens (tabela ou índice secundário) e não quiser retornar ambos de cada &lt;code&gt;Scan&lt;/code&gt;, não há vantagem em mantê-los na mesma tabela. Mas certamente existem algumas desvantagens que abordarei um pouco mais tarde. Para todas as outras operações de dados (incluindo vários itens como &lt;code&gt;BatchGetItem&lt;/code&gt;, &lt;code&gt;TransactWriteItems&lt;/code&gt;, etc), o DynamoDB não se importa se os itens estão na mesma tabela ou não - você verá a mesma medição de armazenamento/taxa de transferência e o mesmo desempenho de qualquer jeito.&lt;/p&gt;

&lt;p&gt;‍As vezes, para reunir os dados na mesma coleção de itens, você precisa fazer alguns ajustes nos valores da chave primária. Para ser armazenado na mesma tabela, a mesma definição de chave primária deve ser usada. Exemplos disso incluem armazenar um valor numérico como uma string para corresponder ao tipo de dados da chave de partição ou chave de ordenação ou criar um valor exclusivo para a chave de ordenação onde você não necessariamente tem algo importante para armazenar (usando o mesmo valor que o atributo de chave de partição é comum para isso ao criar um item de "metadados" na coleção).&lt;/p&gt;

&lt;p&gt;‍&lt;br&gt;
Imagine uma tabela para armazenar pedidos de clientes para envio. A chave de partição é um identificador de pedido numérico exclusivo (o tipo de dados é número) e você deseja usar uma coleção para armazenar um registro por item de itens do carrinho (identificado por um SKU numérico) e eventos de rastreamento (identificados por um UUID ordenável, como &lt;a href="https://github.com/segmentio/ksuid"&gt;ksuid&lt;/a&gt;) para processamento de pedidos. Você deseja recuperá-los juntos para eficiência porque o padrão mais comum é fornecer uma página de rastreamento com os itens comprados mostrados além dos detalhes de rastreamento. Nesse caso, você precisará definir a chave de ordenação com tipo de dados string e converter valores para armazenar esses SKUs numéricos como strings. Há um custo para isso (porque armazenar um número como uma string consome mais bytes), mas vale a pena para obter o benefício da coleção de itens.&lt;/p&gt;

&lt;p&gt;Como é fácil acomodar essas alterações ao escrever seus itens na tabela, um desenvolvedor pode fazer isso apenas para todos os itens e coleções de itens, certo? E colocá-los todos na mesma tabela? Faria sentido para todos os clientes do DynamoDB se unirem e armazenarem seus dados em uma tabela &lt;em&gt;multi-tenant&lt;/em&gt; gigante? Não, claro que não. Essas técnicas têm um custo e só devem ser usadas quando puderem ser justificadas - para colher os benefícios das coleções de itens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finalizando
&lt;/h2&gt;

&lt;p&gt;A parte valiosa e válida da orientação de "design de tabela única" é simplesmente esta:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a flexibilidade de esquema e as coleções de itens do DynamoDB para otimizar seu modelo de dados para os padrões de acesso que você precisa cobrir.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se você estiver migrando de um banco de dados relacional, provavelmente terminará com menos tabelas do que seu antigo modelo totalmente normalizado. Você provavelmente terá mais de uma tabela e deve usar qualquer número de Índices Secundários Globais (GSIs) necessários para atender aos seus padrões secundários.&lt;/p&gt;

&lt;p&gt;Sim, é isso. Realmente nunca houve necessidade de criar uma nova terminologia. O "design de tabela única" confundiu algumas pessoas e levou muitas outras a um caminho doloroso, complexo e caro (detalhes sobre isso em um próximo artigo) quando foi interpretado literalmente e, em seguida, deturpado como "melhor prática".&lt;/p&gt;

&lt;p&gt;É hora de deixar essa terminologia de lado - ficar com a flexibilidade do esquema e as coleções de itens.&lt;/p&gt;




&lt;p&gt;Se você quiser discutir esse tópico comigo, obter minha opinião sobre uma pergunta de modelagem de dados do DynamoDB que você tem ou sugerir tópicos para eu escrever em artigos futuros, entre em contato comigo no Twitter (&lt;a href="https://twitter.com/pj_naylor"&gt;@pj_naylor&lt;/a&gt;) ou envie-me um [e-mail diretamente]mailto:&lt;a href="mailto:petenaylor@momentohq.com"&gt;petenaylor@momentohq.com&lt;/a&gt;)!&lt;/p&gt;




&lt;p&gt;Fique de olho em artigos futuros em que discutirei as nuâncias de LSIs e GSIs e explicarei por que a "sobrecarga de GSI" é um padrão de modelagem de araque.&lt;/p&gt;




&lt;p&gt;Se você quiser discutir esse tópico comigo, obter minha opinião sobre uma pergunta de modelagem de dados do DynamoDB que você tem ou sugerir tópicos para eu escrever em artigos futuros, entre em contato comigo no Twitter (&lt;a href="https://twitter.com/pj_naylor"&gt;@pj_naylor&lt;/a&gt;) ou envie-me um [e-mail diretamente]mailto:&lt;a href="mailto:petenaylor@momentohq.com"&gt;petenaylor@momentohq.com&lt;/a&gt;)!&lt;/p&gt;




&lt;h2&gt;
  
  
  Apêndice: O que eu saberia sobre o DynamoDB afinal?
&lt;/h2&gt;

&lt;p&gt;Até recentemente, eu trabalhava na AWS. Comecei como gerente técnico de contas em 2016 na equipe de contas que oferece suporte à Amazon.com como cliente corporativo da AWS. Minha área de foco era ajudar a Amazon a atingir algumas metas organizacionais ambiciosas: 1) migrar projetos críticos de bancos de dados transacionais de bancos de dados relacionais tradicionais para bancos de dados distribuídos desenvolvidos especificamente para computação em nuvem (como DynamoDB); 2) cargas transacionais de segundo nível "lift and shift" do Oracle para o Aurora; e 3) mover todo o armazenamento de dados do Oracle para o Redshift. Foi uma experiência fantástica em geral, mas a parte que mais gostei foi testemunhar a redução drástica na carga operacional e as melhorias na disponibilidade e latência que o DynamoDB proporcionou. Trabalhei com centenas de equipes de desenvolvedores da Amazon.com para revisar seus modelos de dados, ensiná-los sobre como gerenciar limites, dimensionamento, alarmes e painéis do DynamoDB. Sentei-me nas salas de guerra para eventos de pico, como o Prime Day, como ponto de contato para qualquer tipo de preocupação relacionada ao DynamoDB que surgisse com o rápido crescimento do tráfego de eventos. Não foi fácil para todos fazer a mudança de paradigma do modelo de dados, mas &lt;a href="https://aws.amazon.com/blogs/aws/migration-complete-amazons-consumer-business-just-turned-off-its-final-oracle-database/"&gt;quando esse programa foi concluído&lt;/a&gt;, todos os desenvolvedores rapidamente passaram a considerar todas as vantagens garantidas do DynamoDB. Eles passaram a dedicar muito mais tempo às coisas que faziam a diferença para seus clientes — ver isso me impressionou profundamente.&lt;/p&gt;

&lt;p&gt;Em 2018, tornei-me um arquiteto de soluções especializado em DynamoDB — parte de uma pequena equipe que trabalha com os maiores clientes corporativos (externos) da AWS. Eu os ajudei a desenvolver modelos de dados DynamoDB eficazes e eficientes para uma ampla variedade de casos de uso e padrões de acesso, e ensinei muitos desenvolvedores a operar bem com o DynamoDB. Meus últimos 18 meses na AWS foram gastos como gerente de produto para o serviço DynamoDB. Também dei centenas de consultorias a engenheiros de várias equipes de serviço da AWS como parte de um programa de "office hours" para o DynamoDB — revisando modelos de dados, fornecendo orientação de arquitetura mais ampla e ensinando sobre como o DynamoDB funciona e os insights revelados pelas métricas.&lt;/p&gt;

&lt;p&gt;Tudo isso é para dizer que já vi muitos casos de uso de produção para o DynamoDB (de centenas de solicitações por dia a milhões de solicitações por segundo) e tenho uma forte noção da maneira como os desenvolvedores (na Amazon.com, dentro da AWS e em muitas outras empresas ao redor do mundo) estão realmente usando o DynamoDB na prática.&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>aws</category>
      <category>cloud</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>Construindo Arquiteruras Orientadas a Eventos</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Tue, 14 Mar 2023 00:11:57 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/construindo-arquiteruras-orientadas-a-eventos-4pb</link>
      <guid>https://dev.to/oieduardorabelo/construindo-arquiteruras-orientadas-a-eventos-4pb</guid>
      <description>&lt;p&gt;🇧🇷 Tradução completa do &lt;a href="https://serverlessland.com/event-driven-architecture/intro"&gt;Building Event Driven&lt;br&gt;
Architectures&lt;/a&gt; do &lt;a href="https://serverlessland.com/"&gt;Serverless Land&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Essa é uma tradução livre, não oficial, realizada no meu tempo livre - Eduardo Rabelo.&lt;/p&gt;
&lt;h1&gt;
  
  
  Construindo Arquiteruras Orientadas a Eventos
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://serverlessland.com/event-driven-architecture"&gt;As arquiteturas orientadas a eventos&lt;/a&gt; são um estilo de arquitetura que pode ajudá-lo a aumentar a agilidade de criar aplicativos confiáveis ​​e escaláveis. Serviços serverless como EventBridge, Step Functions, SQS, SNS e Lambda têm uma afinidade natural com arquiteturas orientadas a eventos - elas são invocadas por eventos, emitem eventos e possuem recursos nativos para construir com eventos.&lt;/p&gt;

&lt;p&gt;&lt;a href="//./src/01-01-introducao-a-arquitetura-orientada-a-eventos.md"&gt;Saiba mais&lt;/a&gt; sobre arquiteturas orientadas a eventos, incluindo conceitos-chave, práticas recomendadas, serviços da AWS e recursos de introdução.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Qforsdhr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://serverlessland.com/assets/images/eda/event-producer-broker-consumer.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qforsdhr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://serverlessland.com/assets/images/eda/event-producer-broker-consumer.png" alt="Imagem mostrando comunicação do cliente (produtor de eventos) com o agente de eventos (event broker) e os consumidores de eventos" width="801" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Este guia irá te apresentar a arquiteturas orientadas a eventos. Você vai compreender os padrões das arquiteturas orientadas a eventos e os serviços da AWS usados para implementá-los. Aprenda as práticas recomendadas para criar arquiteturas orientadas a eventos, desde a criação de esquemas de eventos até o tratamento de conceitos como a idempotência. Ao concluir esse guia, você encontrará recursos extras para expandir seu conhecimento em como criar arquiteturas orientadas a eventos na AWS.&lt;/p&gt;
&lt;h1&gt;
  
  
  A tradução completa no GitHub
&lt;/h1&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/oieduardorabelo"&gt;
        oieduardorabelo
      &lt;/a&gt; / &lt;a href="https://github.com/oieduardorabelo/construindo-arquiteruras-orientadas-a-eventos-serverless-land"&gt;
        construindo-arquiteruras-orientadas-a-eventos-serverless-land
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🇧🇷 Tradução completa do Building Event Driven Architectures do Serverless Land.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;🇧🇷 Tradução completa do &lt;a href="https://serverlessland.com/event-driven-architecture/intro" rel="nofollow"&gt;Building Event Driven
Architectures&lt;/a&gt; do &lt;a href="https://serverlessland.com/" rel="nofollow"&gt;Serverless Land&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Essa é uma tradução livre, não oficial, realizada no meu tempo livre - Eduardo Rabelo.&lt;/p&gt;
&lt;h1&gt;
Construindo Arquiteruras Orientadas a Eventos&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://serverlessland.com/event-driven-architecture" rel="nofollow"&gt;As arquiteturas orientadas a eventos&lt;/a&gt; são um estilo de arquitetura que pode ajudá-lo a aumentar a agilidade de criar aplicativos confiáveis ​​e escaláveis. Serviços serverless como EventBridge, Step Functions, SQS, SNS e Lambda têm uma afinidade natural com arquiteturas orientadas a eventos - elas são invocadas por eventos, emitem eventos e possuem recursos nativos para construir com eventos.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/oieduardorabelo/construindo-arquiteruras-orientadas-a-eventos-serverless-land./src/01-01-introducao-a-arquitetura-orientada-a-eventos.md"&gt;Saiba mais&lt;/a&gt; sobre arquiteturas orientadas a eventos, incluindo conceitos-chave, práticas recomendadas, serviços da AWS e recursos de introdução.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/0ea5387beeffc1149a7bd3a0c0a7f99a36959963f94d35f3a26e86d4395c67ec/68747470733a2f2f7365727665726c6573736c616e642e636f6d2f6173736574732f696d616765732f6564612f6576656e742d70726f64756365722d62726f6b65722d636f6e73756d65722e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/0ea5387beeffc1149a7bd3a0c0a7f99a36959963f94d35f3a26e86d4395c67ec/68747470733a2f2f7365727665726c6573736c616e642e636f6d2f6173736574732f696d616765732f6564612f6576656e742d70726f64756365722d62726f6b65722d636f6e73756d65722e706e67" alt="Imagem mostrando comunicação do cliente (produtor de eventos) com o agente de eventos (event broker) e os consumidores de eventos"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Este guia irá te apresentar a arquiteturas orientadas a eventos. Você vai compreender os padrões das arquiteturas orientadas a eventos e os serviços da AWS usados para implementá-los. Aprenda as práticas recomendadas para criar arquiteturas orientadas a eventos, desde a criação…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/oieduardorabelo/construindo-arquiteruras-orientadas-a-eventos-serverless-land"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>braziliandevs</category>
      <category>cloud</category>
      <category>aws</category>
      <category>serverless</category>
    </item>
    <item>
      <title>10 melhores práticas para nomes de arquivos e buckets no Amazon S3</title>
      <dc:creator>Eduardo Rabelo</dc:creator>
      <pubDate>Mon, 13 Mar 2023 03:44:07 +0000</pubDate>
      <link>https://dev.to/oieduardorabelo/10-melhores-praticas-para-nomes-de-arquivos-e-buckets-no-amazon-s3-20eo</link>
      <guid>https://dev.to/oieduardorabelo/10-melhores-praticas-para-nomes-de-arquivos-e-buckets-no-amazon-s3-20eo</guid>
      <description>&lt;h2&gt;
  
  
  O Amazon S3 é um serviço de armazenamento simples que oferece escalabilidade, disponibilidade de dados, segurança e desempenho que são líderes do setor. Ao nomear seus buckets do S3, siga estas práticas recomendadas para garantir um gerenciamento de dados tranquilo e eficiente.
&lt;/h2&gt;

&lt;p&gt;Amazon S3 é um popular serviço de armazenamento em nuvem fornecido pela Amazon Web Services (AWS). O S3 é um armazenamento de valor-chave simples com o benefício adicional de oferecer baixa latência e alta durabilidade.&lt;/p&gt;

&lt;p&gt;Ao trabalhar com o S3, é importante seguir as práticas recomendadas para nomear objetos armazenados nos buckets. Isso ajudará você a manter seus dados organizados e facilitará a localização do que você está procurando.&lt;/p&gt;

&lt;p&gt;Neste artigo, discutiremos as 10 melhores práticas para nomes de arquivos e buckets no Amazon S3.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Use uma convenção de nomenclatura consistente
&lt;/h1&gt;

&lt;p&gt;Quando você tem uma convenção de nomenclatura consistente, é muito mais fácil encontrar os arquivos que está procurando. Por exemplo, se todos os seus arquivos de imagem forem nomeados com a data primeiro, seguida por uma descrição da imagem, será muito mais fácil encontrar uma imagem específica do que se todos fossem nomeados aleatoriamente.&lt;/p&gt;

&lt;p&gt;Também é importante usar uma convenção de nomenclatura consistente porque facilita a automatização de tarefas. Por exemplo, se você sabe que todos os seus arquivos de imagem são nomeados da mesma maneira, você pode facilmente escrever um script que faça backup deles automaticamente em outro local.&lt;/p&gt;

&lt;p&gt;Por fim, usar uma convenção de nomenclatura consistente facilita o compartilhamento de arquivos com outras pessoas. Se você sabe que todos os seus arquivos são nomeados da mesma maneira, basta dizer a alguém para ir ao seu site e procurar o nome do arquivo que está procurando. Isso é muito mais fácil do que tentar explicar onde encontrar um arquivo quando os nomes estão por toda parte.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Evite caracteres especiais em nomes de buckets
&lt;/h1&gt;

&lt;p&gt;Quando você cria um bucket, a Amazon atribui automaticamente a ele um endpoint de site. Este é o URL que você usa para acessar o conteúdo do seu bucket quando está usando o recurso de hospedagem de site estático do S3.&lt;/p&gt;

&lt;p&gt;No entanto, se o nome do seu bucket contiver caracteres especiais, como pontos ou hífens, o endpoint do site não funcionará. Isso significa que qualquer pessoa que tentar visitar seu site receberá uma mensagem de erro.&lt;/p&gt;

&lt;p&gt;Para evitar esse problema, certifique-se de que seus nomes de bucket não contenham nenhum caractere especial.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Não use pontos nos nomes dos buckets
&lt;/h1&gt;

&lt;p&gt;Quando você cria um bucket com um ponto no nome, como &lt;code&gt;"meu.projeto"&lt;/code&gt;, o S3 irá tratá-lo como dois buckets diferentes: &lt;code&gt;"meu"&lt;/code&gt; e &lt;code&gt;"projeto"&lt;/code&gt;. Isso pode causar problemas porque, ao tentar acessar o bucket &lt;code&gt;"meu.projeto"&lt;/code&gt;, você pode ser redirecionado para o bucket &lt;code&gt;"projeto"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Para evitar esse problema, simplesmente não use pontos em seus nomes de bucket.&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Os nomes dos buckets são globalmente únicos
&lt;/h1&gt;

&lt;p&gt;Quando você cria um novo bucket, o Amazon S3 verifica se o nome escolhido está disponível como um subdomínio DNS (Domain Name System). Se o nome estiver disponível, o Amazon S3 atribuirá esse nome ao seu bucket. No entanto, se o nome escolhido não estiver disponível, o Amazon S3 retornará uma mensagem de erro.&lt;/p&gt;

&lt;p&gt;Isso é importante porque significa que você poderia criar inadvertidamente dois buckets com o mesmo nome e esses buckets seriam acessíveis a partir de URLs diferentes. Por exemplo, digamos que você crie um bucket chamado &lt;code&gt;"example-bucket"&lt;/code&gt; na região Leste dos EUA (N. Virgínia, &lt;code&gt;us-east-1&lt;/code&gt;). Em seguida, outra pessoa cria um bucket com o mesmo nome na região da UE (Irlanda, &lt;code&gt;eu-west-1&lt;/code&gt;). Embora sejam dois buckets diferentes, ambos podem ser acessados ​​usando a URL &lt;code&gt;"example-bucket.s3.amazonaws.com"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Para evitar esse problema, escolha um nome único para seu bucket e use o identificador de região ao criar buckets em diferentes regiões. Por exemplo, você pode nomear seu bucket "example-bucket-us-east-1" na região US East (N. Virginia) e "example-bucket-eu-west-1" na região EU (Irlanda).&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Mantenha seus buckets organizados
&lt;/h1&gt;

&lt;p&gt;Quando você tem muitos dados no S3, pode ser difícil acompanhar tudo sem uma estrutura organizacional clara. Ao usar uma convenção de nomenclatura consistente para seus buckets, você pode garantir que todos os seus dados sejam fáceis de localizar e gerenciar.&lt;/p&gt;

&lt;p&gt;Uma boa maneira de organizar seus buckets é usar um prefixo ou sufixo que indique o tipo de dados armazenados em cada bucket. Por exemplo, você pode usar o prefixo &lt;code&gt;"dados-brutos"&lt;/code&gt; (&lt;em&gt;raw-data&lt;/em&gt;) para buckets contendo arquivos não tratados, &lt;code&gt;"dados-processados"&lt;/code&gt; para buckets contendo arquivos de dados processados ​​e assim por diante.&lt;/p&gt;

&lt;p&gt;Ao usar uma convenção de nomenclatura consistente, você pode tornar mais fácil para você e para outras pessoas encontrar e usar os dados armazenados em seus buckets do S3.&lt;/p&gt;

&lt;h1&gt;
  
  
  6. Crie e aplique políticas para o gerenciamento do ciclo de vida de objetos no S3
&lt;/h1&gt;

&lt;p&gt;Os objetos no S3, por padrão, são colocados em uma classe de armazenamento &lt;em&gt;Standard&lt;/em&gt;, o que significa que eles são armazenados indefinidamente, a menos que você especifique uma classe de armazenamento diferente ou exclua o objeto. No entanto, você pode não querer manter todos os objetos do S3 para sempre. Por exemplo, você pode precisar acessar determinados objetos apenas por um tempo limitado, após o qual poderá excluí-los.&lt;/p&gt;

&lt;p&gt;A criação e aplicação de políticas para o gerenciamento do ciclo de vida de objetos no S3 permite que você exclua objetos automaticamente após um determinado período de tempo, o que pode ajudar a economizar nos custos de armazenamento. Ele também pode ajudar a melhorar a segurança, garantindo que objetos antigos e não utilizados não sejam deixados espalhados onde possam ser acessados ​​por usuários não autorizados.&lt;/p&gt;

&lt;p&gt;Para criar e aplicar políticas para o gerenciamento do ciclo de vida do objeto S3, você pode usar o AWS Management Console, os SDKs da AWS ou a AWS Command Line Interface (AWS CLI).&lt;/p&gt;

&lt;h1&gt;
  
  
  7. Habilite o versionamento em todos os buckets
&lt;/h1&gt;

&lt;p&gt;O controle de versão é um recurso crítico do S3 que permite manter várias versões de um objeto no mesmo bucket. Isso é útil por vários motivos, como poder reverter para versões anteriores de um objeto ou recuperar-se de exclusões acidentais.&lt;/p&gt;

&lt;p&gt;Habilitar o controle de versão em um bucket é fácil e leva apenas alguns cliques no console AWS. Depois de ativados, todos os objetos armazenados no bucket terão seu próprio ID de versão única. Esses IDs podem ser usados ​​para recuperar versões específicas de um objeto quando necessário.&lt;/p&gt;

&lt;h1&gt;
  
  
  8. Criptografar dados em repouso (&lt;em&gt;Encrypt data at rest&lt;/em&gt;)
&lt;/h1&gt;

&lt;p&gt;Quando os dados são armazenados no S3, eles são armazenados fisicamente em servidores pertencentes e operados pela Amazon. Embora a Amazon faça um ótimo trabalho protegendo seus servidores, eles não podem garantir que os servidores nunca sejam comprometidos.&lt;/p&gt;

&lt;p&gt;Se os dados forem criptografados em repouso, mesmo que os servidores sejam comprometidos, os dados ainda estarão seguros porque serão ilegíveis sem as chaves de criptografia.&lt;/p&gt;

&lt;p&gt;Há duas maneiras principais de criptografar dados em repouso no S3: criptografia do lado do servidor e criptografia do lado do cliente.&lt;/p&gt;

&lt;p&gt;A criptografia do lado do servidor é quando a Amazon criptografa os dados antes de serem gravados no servidor. Os dados são descriptografados automaticamente quando são lidos do servidor.&lt;/p&gt;

&lt;p&gt;A criptografia do lado do cliente é quando os dados são criptografados pelo cliente antes de serem enviados para a Amazon. Os dados permanecem criptografados enquanto são armazenados no servidor e só são descriptografados quando o cliente os recupera.&lt;/p&gt;

&lt;p&gt;Ambos os métodos são igualmente seguros, mas a criptografia do lado do cliente requer mais trabalho para configurar.&lt;/p&gt;

&lt;h1&gt;
  
  
  9. Monitore e audite o acesso aos seus buckets
&lt;/h1&gt;

&lt;p&gt;Se você não estiver monitorando e auditando o acesso aos seus buckets, não terá como saber quem está acessando seus dados ou o que eles estão fazendo com eles. Isso pode levar a violações de dados, vazamentos de dados ou até mesmo perda de dados.&lt;/p&gt;

&lt;p&gt;Para evitar esses riscos, você deve sempre monitorar e auditar o acesso aos seus buckets do S3. A AWS fornece um serviço chamado CloudTrail que facilita isso. Com o CloudTrail, você pode ver quem acessou seus buckets, o que eles fizeram e quando o fizeram.&lt;/p&gt;

&lt;p&gt;Você também pode usar o CloudTrail para configurar alertas para ser notificado imediatamente se alguém tentar acessar seus buckets sem permissão. Dessa forma, você pode agir rapidamente para evitar qualquer dano.&lt;/p&gt;

&lt;h1&gt;
  
  
  10. Limite o acesso público aos seus buckets
&lt;/h1&gt;

&lt;p&gt;Quando você cria um novo bucket do S3, ele é automaticamente privado. No entanto, existem várias maneiras de alguém tornar seu bucket público sem que você perceba. Por exemplo, se você usar um console do Amazon S3 para fazer upload de arquivos para um bucket e esquecer de definir as permissões, qualquer pessoa poderá acessar esses arquivos.&lt;/p&gt;

&lt;p&gt;Para evitar isso, é importante limitar o acesso público aos seus buckets. Você pode fazer isso configurando uma política de bucket que nega todo o acesso público ou usando Amazon S3 Block Public Access.&lt;/p&gt;

&lt;p&gt;Ambos os métodos ajudarão a garantir que apenas usuários autorizados possam acessar seus buckets S3 e que seus dados estejam seguros.&lt;/p&gt;




&lt;h1&gt;
  
  
  Créditos
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Escrito originalmente por &lt;a href="https://climbtheladder.com/users/1762/profile/daryl-pilkington/"&gt;Janet Maples&lt;/a&gt;, em &lt;a href="https://climbtheladder.com/10-s3-bucket-naming-convention-best-practices/"&gt;10 S3 Naming Convention Best Practices&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>braziliandevs</category>
      <category>cloud</category>
      <category>aws</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
