<?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>Forem: Dilip Kola</title>
    <description>The latest articles on Forem by Dilip Kola (@koladilip).</description>
    <link>https://forem.com/koladilip</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F857902%2Fef8198ac-725a-45f2-9b49-61946242c216.jpeg</url>
      <title>Forem: Dilip Kola</title>
      <link>https://forem.com/koladilip</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/koladilip"/>
    <language>en</language>
    <item>
      <title>Implementing a Cost-Efficient Micro services Platform on Azure Kubernetes</title>
      <dc:creator>Dilip Kola</dc:creator>
      <pubDate>Mon, 19 Jan 2026 04:39:04 +0000</pubDate>
      <link>https://forem.com/koladilip/implementing-a-cost-efficient-microservices-platform-on-azure-kubernetes-45jm</link>
      <guid>https://forem.com/koladilip/implementing-a-cost-efficient-microservices-platform-on-azure-kubernetes-45jm</guid>
      <description>&lt;h2&gt;
  
  
  Terraform, Autoscaling, Spot Capacity, and Workload Identity
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This article focuses on implementation details. Architectural rationale and cost trade-offs are covered in &lt;a href="https://dev.to/koladilip/building-production-grade-microservices-on-azure-kubernetes-581o"&gt;Part 1&lt;/a&gt;.&lt;/em&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.amazonaws.com%2Fuploads%2Farticles%2F2u6t1423e78erk9fb9de.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.amazonaws.com%2Fuploads%2Farticles%2F2u6t1423e78erk9fb9de.png" alt="Implementing cost-efficient microservices on AKS" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Scope and Assumptions
&lt;/h2&gt;

&lt;p&gt;This post assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Familiarity with Kubernetes fundamentals&lt;/li&gt;
&lt;li&gt;Comfort reading Terraform and Helm&lt;/li&gt;
&lt;li&gt;Interest in &lt;strong&gt;operating&lt;/strong&gt; systems, not just deploying them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The platform runs on &lt;strong&gt;Azure Kubernetes Service&lt;/strong&gt;, provisioned with &lt;strong&gt;Terraform&lt;/strong&gt;, and deployed using &lt;strong&gt;Helm&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. AKS Baseline: Start Small, Scale on Demand
&lt;/h2&gt;

&lt;p&gt;The most common AKS cost mistake is provisioning for peak load.&lt;/p&gt;

&lt;p&gt;We instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with minimal baseline capacity&lt;/li&gt;
&lt;li&gt;Enable the cluster autoscaler&lt;/li&gt;
&lt;li&gt;Let demand drive node count&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AKS Cluster with Autoscaling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_kubernetes_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"aks"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
  &lt;span class="nx"&gt;dns_prefix&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;

  &lt;span class="nx"&gt;default_node_pool&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="s2"&gt;"default"&lt;/span&gt;
    &lt;span class="nx"&gt;vm_size&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard_D2s_v5"&lt;/span&gt;
    &lt;span class="nx"&gt;auto_scaling_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;min_count&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nx"&gt;max_count&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;identity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SystemAssigned"&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;&lt;strong&gt;Why this works&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Idle cost remains low&lt;/li&gt;
&lt;li&gt;Nodes are added only when pods are pending&lt;/li&gt;
&lt;li&gt;Capacity matches actual demand, not estimates&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Horizontal Pod Autoscaling with Predictable Behavior
&lt;/h2&gt;

&lt;p&gt;Autoscaling defaults are aggressive and often unstable.&lt;/p&gt;

&lt;p&gt;We explicitly tune scale behavior to reduce churn and latency spikes.&lt;/p&gt;

&lt;h3&gt;
  
  
  HPA with Stabilization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;autoscaling/v2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HorizontalPodAutoscaler&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;api-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&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;api-service&lt;/span&gt;
  &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Resource&lt;/span&gt;
    &lt;span class="na"&gt;resource&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;cpu&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Utilization&lt;/span&gt;
        &lt;span class="na"&gt;averageUtilization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;70&lt;/span&gt;
  &lt;span class="na"&gt;behavior&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;scaleDown&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;stabilizationWindowSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key outcomes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevents rapid scale-down during brief traffic dips&lt;/li&gt;
&lt;li&gt;Improves tail latency&lt;/li&gt;
&lt;li&gt;Reduces unnecessary pod restarts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Spot Node Pools for Fault-Tolerant Workloads
&lt;/h2&gt;

&lt;p&gt;Spot capacity is one of the highest-leverage cost optimizations—when isolated properly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terraform: Spot Node Pool
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_kubernetes_cluster_node_pool"&lt;/span&gt; &lt;span class="s2"&gt;"spot"&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="s2"&gt;"spot"&lt;/span&gt;
  &lt;span class="nx"&gt;kubernetes_cluster_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;vm_size&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard_D2as_v5"&lt;/span&gt;

  &lt;span class="nx"&gt;priority&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Spot"&lt;/span&gt;
  &lt;span class="nx"&gt;eviction_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Delete"&lt;/span&gt;
  &lt;span class="nx"&gt;spot_max_price&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;-1&lt;/span&gt;

  &lt;span class="nx"&gt;auto_scaling_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;min_count&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="nx"&gt;max_count&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

  &lt;span class="nx"&gt;node_taints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.azure.com/scalesetpriority=spot:NoSchedule"&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;h3&gt;
  
  
  Scheduling Workers on Spot Nodes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tolerations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubernetes.azure.com/scalesetpriority"&lt;/span&gt;
  &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Equal"&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spot"&lt;/span&gt;
  &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NoSchedule"&lt;/span&gt;

&lt;span class="na"&gt;nodeSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kubernetes.azure.com/scalesetpriority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rules we followed&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs never run on spot&lt;/li&gt;
&lt;li&gt;Workers handle &lt;code&gt;SIGTERM&lt;/code&gt; cleanly&lt;/li&gt;
&lt;li&gt;All state lives outside the worker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Used this way, spot capacity delivered substantial savings without user-visible impact.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Managed PostgreSQL with Private Networking
&lt;/h2&gt;

&lt;p&gt;Databases are not where cost experiments belong.&lt;/p&gt;

&lt;p&gt;PostgreSQL runs as a managed service with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subnet delegation&lt;/li&gt;
&lt;li&gt;Private DNS&lt;/li&gt;
&lt;li&gt;No public access&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Delegated Subnet for PostgreSQL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"postgres"&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="s2"&gt;"postgres-subnet"&lt;/span&gt;
  &lt;span class="nx"&gt;virtual_network_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_virtual_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
  &lt;span class="nx"&gt;address_prefixes&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;delegation&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="s2"&gt;"postgres"&lt;/span&gt;
    &lt;span class="nx"&gt;service_delegation&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="s2"&gt;"Microsoft.DBforPostgreSQL/flexibleServers"&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;&lt;strong&gt;Why this matters&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database is unreachable from the public internet&lt;/li&gt;
&lt;li&gt;Access is restricted at the network layer&lt;/li&gt;
&lt;li&gt;Operational risk is significantly reduced&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Secure Deployments with Helm Defaults
&lt;/h2&gt;

&lt;p&gt;Helm charts were written with &lt;strong&gt;secure-by-default&lt;/strong&gt; assumptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pod Security Context
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
  &lt;span class="na"&gt;runAsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
  &lt;span class="na"&gt;allowPrivilegeEscalation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;readOnlyRootFilesystem&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;This immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shrinks the attack surface&lt;/li&gt;
&lt;li&gt;Prevents runtime mutation&lt;/li&gt;
&lt;li&gt;Surfaces insecure images early&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Health Probes That Matter
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/health/db-cache&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We intentionally check &lt;strong&gt;dependencies&lt;/strong&gt;, not just process health.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Workload Identity: No Secrets in Kubernetes
&lt;/h2&gt;

&lt;p&gt;Storing cloud credentials in Kubernetes secrets is unnecessary.&lt;/p&gt;

&lt;p&gt;We use &lt;strong&gt;Workload Identity&lt;/strong&gt; for pod-to-Azure authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Federated Identity Credential
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_federated_identity_credential"&lt;/span&gt; &lt;span class="s2"&gt;"api"&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="s2"&gt;"api-federated"&lt;/span&gt;
  &lt;span class="nx"&gt;parent_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_user_assigned_identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;issuer&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc_issuer_url&lt;/span&gt;
  &lt;span class="nx"&gt;subject&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"system:serviceaccount:production:api-sa"&lt;/span&gt;
  &lt;span class="nx"&gt;audience&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"api://AzureADTokenExchange"&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;h3&gt;
  
  
  Kubernetes Service Account
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;api-sa&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;azure.workload.identity/client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;client-id&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No long-lived secrets in manifests&lt;/li&gt;
&lt;li&gt;Automatic token rotation&lt;/li&gt;
&lt;li&gt;Azure RBAC enforced at runtime&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Observability Without Ingestion-Based Pricing
&lt;/h2&gt;

&lt;p&gt;Instead of managed log ingestion, we use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prometheus for metrics&lt;/li&gt;
&lt;li&gt;Loki for logs&lt;/li&gt;
&lt;li&gt;Object storage for retention&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Loki Storage Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;storage_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;azure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;logs&lt;/span&gt;
    &lt;span class="na"&gt;account_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${ACCOUNT_NAME}&lt;/span&gt;
    &lt;span class="na"&gt;access_tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logs are queried infrequently&lt;/li&gt;
&lt;li&gt;Storage is inexpensive&lt;/li&gt;
&lt;li&gt;Ingestion costs dominate managed observability pricing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This preserved full visibility with minimal incremental cost.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Infrastructure Access Patterns (Secure and Practical)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cluster Access
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Azure AD–backed &lt;code&gt;kubectl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Auditable&lt;/li&gt;
&lt;li&gt;No shared credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;In practice, cluster-admin access is restricted to a small bootstrap group; most teams use namespace-scoped roles.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Database Access (Occasional Admin Tasks)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run psql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres:16 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; psql &lt;span class="nt"&gt;-h&lt;/span&gt; &amp;lt;private-host&amp;gt; &lt;span class="nt"&gt;-U&lt;/span&gt; admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database remains private; access is authenticated and auditable.&lt;/p&gt;




&lt;h3&gt;
  
  
  Observability Dashboards
&lt;/h3&gt;

&lt;p&gt;Grafana is protected behind OAuth using Azure AD.&lt;br&gt;
OAuth2-Proxy runs with multiple replicas and rotated cookie secrets to avoid becoming a single point of failure.&lt;/p&gt;

&lt;p&gt;No VPNs. No bastion hosts. No additional managed services required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Operational Lessons (The Real Ones)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Defaults Are Rarely Production-Safe
&lt;/h3&gt;

&lt;p&gt;Autoscaling, probes, and security contexts need explicit tuning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spot Capacity Requires Discipline
&lt;/h3&gt;

&lt;p&gt;It works extremely well—but only when isolated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identity Scales Better Than Secrets
&lt;/h3&gt;

&lt;p&gt;It reduces operational load and security risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubernetes Is a Tool, Not a Destination
&lt;/h3&gt;

&lt;p&gt;Use it where it adds leverage—not as a dumping ground.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;This implementation isn’t about clever tricks—it’s about &lt;strong&gt;intentional trade-offs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaling only when needed&lt;/li&gt;
&lt;li&gt;Using spot capacity responsibly&lt;/li&gt;
&lt;li&gt;Keeping critical state managed&lt;/li&gt;
&lt;li&gt;Avoiding ingestion-based observability costs&lt;/li&gt;
&lt;li&gt;Treating security as a default&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we ended up with a system that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Predictable to operate&lt;/li&gt;
&lt;li&gt;Cost-efficient at idle&lt;/li&gt;
&lt;li&gt;Resilient under load&lt;/li&gt;
&lt;li&gt;Easy to evolve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecture sets direction.&lt;br&gt;
Implementation determines whether it survives contact with reality.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>infrastructure</category>
      <category>microservices</category>
      <category>azure</category>
    </item>
    <item>
      <title>Building Production-Grade Microservices on Azure Kubernetes</title>
      <dc:creator>Dilip Kola</dc:creator>
      <pubDate>Mon, 19 Jan 2026 03:44:17 +0000</pubDate>
      <link>https://forem.com/koladilip/building-production-grade-microservices-on-azure-kubernetes-581o</link>
      <guid>https://forem.com/koladilip/building-production-grade-microservices-on-azure-kubernetes-581o</guid>
      <description>&lt;h2&gt;
  
  
  Architecture and Cost Trade-offs
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;How we designed a scalable, reliable microservices platform on Azure Kubernetes Service while keeping infrastructure costs predictable and operational overhead low.&lt;/em&gt;&lt;/p&gt;

&lt;h2&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.amazonaws.com%2Fuploads%2Farticles%2Ft8qfggxfuxwcumz9zj39.png" alt="Azure Kubernetes hybrid microservices architecture" width="800" height="533"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When building cloud-native systems, teams often face a familiar tension:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;reliability versus cost&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fully managed services offer strong SLAs and peace of mind, but their pricing compounds quickly as systems grow. Self-hosted alternatives reduce spend, yet introduce operational complexity that many teams underestimate.&lt;/p&gt;

&lt;p&gt;In this article, we share how we designed a &lt;strong&gt;production-grade microservices platform on Azure Kubernetes Service (AKS)&lt;/strong&gt; using a &lt;strong&gt;hybrid architecture&lt;/strong&gt;—combining managed services where reliability is non-negotiable and self-hosting components where the risk-to-cost trade-off is acceptable.&lt;/p&gt;

&lt;p&gt;This approach resulted in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;70–90% lower monthly infrastructure costs&lt;/strong&gt; compared to fully managed stacks&lt;/li&gt;
&lt;li&gt;Strong reliability guarantees for critical data&lt;/li&gt;
&lt;li&gt;Full observability without per-GB ingestion fees&lt;/li&gt;
&lt;li&gt;A portable, infrastructure-as-code-driven foundation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post focuses on &lt;strong&gt;architecture and decision-making&lt;/strong&gt;.&lt;br&gt;
Implementation details (Terraform, Helm, autoscaling, identity) are covered in &lt;strong&gt;Part 2&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Problem: Cost, Reliability, and Operational Load
&lt;/h2&gt;

&lt;p&gt;Modern microservices platforms are more than application code. Even relatively small systems require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Databases, caches, and message queues&lt;/li&gt;
&lt;li&gt;Stateless application services (APIs, workers, frontend)&lt;/li&gt;
&lt;li&gt;Metrics, logs, and dashboards&lt;/li&gt;
&lt;li&gt;Secure networking and access controls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each component introduces the same decision:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should this be a managed service—or something we run ourselves?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Cost Reality
&lt;/h3&gt;

&lt;p&gt;Individually, managed services are reasonably priced. Together, they add up quickly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Managed Option&lt;/th&gt;
&lt;th&gt;Typical Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Azure PostgreSQL&lt;/td&gt;
&lt;td&gt;$65–200 / month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Azure Cache for Redis&lt;/td&gt;
&lt;td&gt;$50–150 / month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RabbitMQ&lt;/td&gt;
&lt;td&gt;Managed broker&lt;/td&gt;
&lt;td&gt;$100–300 / month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logging&lt;/td&gt;
&lt;td&gt;Azure Monitor Logs&lt;/td&gt;
&lt;td&gt;$2.50 per GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For early-stage products or cost-sensitive deployments, this pricing model can become a constraint long before scale becomes a problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Key Insight: Not All State Is Equal
&lt;/h2&gt;

&lt;p&gt;The most important architectural decision we made was &lt;strong&gt;classifying state&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two Categories of State
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Irreplaceable state&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transactional data&lt;/li&gt;
&lt;li&gt;User data&lt;/li&gt;
&lt;li&gt;Financial records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This data cannot be regenerated. It requires backups, point-in-time recovery, and strong durability guarantees.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regenerable state&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Caches&lt;/li&gt;
&lt;li&gt;Queues&lt;/li&gt;
&lt;li&gt;Derived logs and metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This data can be rebuilt, replayed, or tolerated if lost temporarily.&lt;/p&gt;

&lt;p&gt;Once this distinction is made, the managed vs self-hosted decision becomes much clearer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Our Hybrid Architecture Strategy
&lt;/h2&gt;

&lt;p&gt;Rather than going “all managed” or “all self-hosted,” we adopted a &lt;strong&gt;selective hybrid approach&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managed Services
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL (database of record)&lt;/strong&gt;
Chosen for durability, automated backups, and operational guarantees.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  In-Cluster Services (Kubernetes)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; for caching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RabbitMQ&lt;/strong&gt; for asynchronous workloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability stack&lt;/strong&gt; (metrics and logs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These components are important, but failure is recoverable. Kubernetes handles restarts and rescheduling, making this trade-off reasonable.&lt;/p&gt;

&lt;p&gt;This single decision accounted for the majority of cost savings—without increasing the blast radius of real failures.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kubernetes-First, Not Kubernetes-Everything
&lt;/h2&gt;

&lt;p&gt;Kubernetes serves as the &lt;strong&gt;orchestration layer&lt;/strong&gt;, not a universal hosting solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Kubernetes Adds Value
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Consistent deployment and scaling model&lt;/li&gt;
&lt;li&gt;Horizontal scaling built-in&lt;/li&gt;
&lt;li&gt;Strong ecosystem and tooling&lt;/li&gt;
&lt;li&gt;Cloud-agnostic abstraction&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where It Does Not
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Primary databases&lt;/li&gt;
&lt;li&gt;Systems requiring strict consistency and durability guarantees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using Kubernetes for compute and managed services for critical state strikes a pragmatic balance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scaling Without Paying for Idle Capacity
&lt;/h2&gt;

&lt;p&gt;One of the most common cloud cost mistakes is provisioning for peak traffic.&lt;/p&gt;

&lt;p&gt;Our approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep a small baseline footprint&lt;/li&gt;
&lt;li&gt;Scale pods horizontally based on load&lt;/li&gt;
&lt;li&gt;Allow nodes to scale only when required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At idle, the platform remains inexpensive. During traffic spikes, it scales automatically—and scales back down afterward.&lt;/p&gt;

&lt;p&gt;This keeps costs proportional to actual usage rather than theoretical peak demand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using Spot Capacity—Deliberately
&lt;/h2&gt;

&lt;p&gt;Not every workload requires guaranteed availability.&lt;/p&gt;

&lt;p&gt;Background workers, batch processing, and asynchronous jobs can tolerate interruptions. These workloads run on &lt;strong&gt;spot capacity&lt;/strong&gt;, trading availability guarantees for steep discounts.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Runs on Spot
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Workers&lt;/li&gt;
&lt;li&gt;Batch jobs&lt;/li&gt;
&lt;li&gt;Non-user-facing tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Never Does
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;li&gt;Stateful services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By isolating these workloads, we significantly reduced compute costs without affecting user experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Observability Without Ingestion-Based Pricing
&lt;/h2&gt;

&lt;p&gt;Observability is not optional—but ingestion-based pricing makes it expensive at scale.&lt;/p&gt;

&lt;p&gt;Instead of paying per-GB ingestion fees, we use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open-source tooling for metrics and logs&lt;/li&gt;
&lt;li&gt;Object storage for long-term retention&lt;/li&gt;
&lt;li&gt;Dashboards tailored to the system’s needs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why This Works
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Logs are queried infrequently&lt;/li&gt;
&lt;li&gt;Storage is inexpensive&lt;/li&gt;
&lt;li&gt;Ingestion costs dominate managed observability pricing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach preserves full visibility while keeping observability spend negligible relative to compute.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security and Access: Identity-First by Design
&lt;/h2&gt;

&lt;p&gt;Security decisions were guided by a few core principles:&lt;/p&gt;

&lt;h3&gt;
  
  
  Private by Default
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Databases are reachable only within the virtual network&lt;/li&gt;
&lt;li&gt;Internal services are never exposed publicly&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Identity Over Secrets
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Workloads authenticate using cloud identity&lt;/li&gt;
&lt;li&gt;No long-lived credentials stored in Kubernetes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Least Privilege
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Day-to-day access uses minimal permissions&lt;/li&gt;
&lt;li&gt;Elevated access is required only during initial setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This avoids VPN sprawl, bastion hosts, and unnecessary operational overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cost Snapshot (Production)
&lt;/h2&gt;

&lt;p&gt;A representative production environment looked roughly like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AKS compute (baseline)&lt;/td&gt;
&lt;td&gt;$80–100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed PostgreSQL&lt;/td&gt;
&lt;td&gt;$65&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage &amp;amp; networking&lt;/td&gt;
&lt;td&gt;$30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistent volumes&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$190&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Comparable fully managed setups typically exceeded $500–$1,500 per month.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Don’t Optimize the Wrong Layer
&lt;/h3&gt;

&lt;p&gt;Saving money on the database of record is rarely worth the risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Spot Capacity Is High-Leverage
&lt;/h3&gt;

&lt;p&gt;Used correctly, it provides some of the largest cost reductions available.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Observability Is Mandatory
&lt;/h3&gt;

&lt;p&gt;Skipping it to save money always costs more later.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Infrastructure as Code Pays Off Early
&lt;/h3&gt;

&lt;p&gt;Teams that automate early spend less time firefighting later.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Kubernetes Is an Enabler, Not the Goal
&lt;/h3&gt;

&lt;p&gt;Use it where it adds leverage—not as a default for everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Production-grade microservices don’t require premium managed services at every layer.&lt;/p&gt;

&lt;p&gt;By:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Classifying state correctly&lt;/li&gt;
&lt;li&gt;Using managed services selectively&lt;/li&gt;
&lt;li&gt;Scaling dynamically&lt;/li&gt;
&lt;li&gt;Leveraging open-source observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;it’s possible to build systems that are &lt;strong&gt;reliable, cost-efficient, secure, and portable&lt;/strong&gt;—even with small teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/koladilip/implementing-a-cost-efficient-microservices-platform-on-azure-kubernetes-45jm"&gt;Part 2&lt;/a&gt;&lt;/strong&gt; dives into the implementation details: Terraform modules, AKS autoscaling, spot node pools, Workload Identity, and Kubernetes deployment patterns.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>infrastructure</category>
      <category>microservices</category>
      <category>azure</category>
    </item>
    <item>
      <title>Empowering Real-Time Data Pipelines: Leveraging Apache Kafka and Rudderstack</title>
      <dc:creator>Dilip Kola</dc:creator>
      <pubDate>Tue, 19 Mar 2024 04:45:20 +0000</pubDate>
      <link>https://forem.com/koladilip/empowering-real-time-data-pipelines-leveraging-apache-kafka-and-rudderstack-249d</link>
      <guid>https://forem.com/koladilip/empowering-real-time-data-pipelines-leveraging-apache-kafka-and-rudderstack-249d</guid>
      <description>&lt;p&gt;In today’s fast-paced digital landscape, effective data management and analysis are essential for businesses aiming to stay ahead of the curve. Fortunately, modern tools like &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Apache Kafka&lt;/a&gt; and &lt;a href="https://www.rudderstack.com/" rel="noopener noreferrer"&gt;RudderStack&lt;/a&gt; have revolutionized the way we handle and derive insights from large datasets. In this blog post, we’ll explore our experience implementing the Kafka Sink Connector to facilitate seamless event data transfer to RudderStack, unlocking significant advantages for real-time analytics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apache Kafka: The Backbone of Real-Time Data Streaming
&lt;/h3&gt;

&lt;p&gt;Apache Kafka has emerged as a distributed event streaming platform renowned for its ability to handle real-time data streams efficiently. With a robust architecture capable of processing millions of events per second, Kafka is the go-to solution for systems requiring real-time operations and monitoring. Its versatility spans various data types, including page views, clicks, likes, searches, transactions, and more. Using topics, Kafka securely stores each record in a fault-tolerant manner, facilitating streamlined data storage and stream processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  RudderStack: Unifying Customer Data for Actionable Insights
&lt;/h3&gt;

&lt;p&gt;RudderStack stands out as a customer data platform (CDP) designed to streamline the collection, processing, and routing of customer event data to preferred analytics tools. This open-source, warehouse-first platform empowers businesses to establish their customer data lake directly on their data warehouse, enabling secure and real-time data utilization on their terms.&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.amazonaws.com%2Fuploads%2Farticles%2Fqg6r8q7dmynvls0ld6x1.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.amazonaws.com%2Fuploads%2Farticles%2Fqg6r8q7dmynvls0ld6x1.png" alt="Rudderstack Data Pipelines" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bridging Systems Seamlessly: The Role of Kafka Connectors
&lt;/h3&gt;

&lt;p&gt;Kafka Connect serves as a vital conduit for scalable and reliable data streaming between Apache Kafka and various other systems. Categorized into source and sink connectors, Kafka Connect simplifies the process of defining connectors to seamlessly move large datasets into and out of Kafka.&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.amazonaws.com%2Fuploads%2Farticles%2F746zwidz18glju29skrq.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.amazonaws.com%2Fuploads%2Farticles%2F746zwidz18glju29skrq.png" alt="Kafka Connectors" width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Unlocking Seamless Integration: The Kafka Sink Connector
&lt;/h3&gt;

&lt;p&gt;At the forefront of this integration is the Kafka Sink Connector, a Kafka Connect plugin that acts as a bridge between Apache Kafka and RudderStack. This connector is powered by the RudderStack Java SDK, enabling the seamless export of data directly from Kafka topics to the RudderStack platform. Operating within the Kafka ecosystem, the connector efficiently consumes data from Kafka topics and transmits messages to RudderStack using the Java SDK.&lt;/p&gt;

&lt;p&gt;Explore the Source Code: &lt;a href="https://github.com/rudderlabs/rudder-kafka-sink-connector" rel="noopener noreferrer"&gt;RudderStack Kafka Sink Connector&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During the configuration process, you’ll need to specify crucial details such as RudderStack’s Data Plane URL, Write Key, and Kafka topic names, among other necessary information. Once these configurations are set, the connector seamlessly captures messages from Kafka topics and securely delivers them to RudderStack. From there, RudderStack efficiently processes the incoming data, enabling seamless streaming to over 200 integrations supported by the RudderStack platform. This streamlined process ensures that your data flows seamlessly from Kafka to RudderStack and beyond, unlocking a wide array of integration possibilities for your analytics and business intelligence needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In today’s data-driven landscape, efficient data management and transfer are fundamental to success. Implementing the Kafka Sink Connector streamlines the process of sending events from Kafka to RudderStack, facilitating more effective data analysis and informed decision-making. Empower your business to capitalize on the power of real-time data and gain a competitive edge. Stay tuned for further insights and explorations as we delve deeper into the realm of data management and analytics solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Originally posted &lt;a href="https://medium.com/@koladilip/empowering-real-time-data-pipelines-leveraging-apache-kafka-and-ruddestack-c9974c0e0192" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>streaming</category>
      <category>sinkconnector</category>
      <category>data</category>
    </item>
    <item>
      <title>Template Language for JSON data</title>
      <dc:creator>Dilip Kola</dc:creator>
      <pubDate>Wed, 08 Mar 2023 18:26:20 +0000</pubDate>
      <link>https://forem.com/koladilip/template-language-for-json-data-4g6n</link>
      <guid>https://forem.com/koladilip/template-language-for-json-data-4g6n</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;We are an integration platform and support 200+ integrations. We implemented these integrations using native Javascript code to transform incoming events to destination payload, so in summary, it is JSON data manipulation. Maintaining all these integrations is challenging, so we explored &lt;a href="https://github.com/jsonata-js/jsonata" rel="noopener noreferrer"&gt;jsonata&lt;/a&gt; to write less code to transform JSON data. While this library is excellent, we still need to meet our performance needs. For example, JSONata parses the given template, creates an Abstract Syntax Tree (AST), and interprets the AST for the given input. Since we need to traverse the AST every time, it is slow, so we wanted to build a template engine that generates Javascript code so there will be less overhead during the runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;A library to process JSON data using a custom syntax based on javascript and &lt;a href="https://github.com/dfilatov/jspath" rel="noopener noreferrer"&gt;jspath&lt;/a&gt;. We thank the jspath authors for their excellent work, as our library is an extension of the original library. We also want to thank &lt;a href="https://www.ibm.com/" rel="noopener noreferrer"&gt;IBM&lt;/a&gt; team for their work on &lt;a href="https://github.com/jsonata-js/jsonata" rel="noopener noreferrer"&gt;jsonata&lt;/a&gt;, as we have taken several ideas from the library. You can also consider our library as an alternative to &lt;a href="https://github.com/jsonata-js/jsonata" rel="noopener noreferrer"&gt;jsonata&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This library generates a javascript function code from the template and then uses the function to evaluate the JSON data. It outputs the javascript code in the following stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="//src/lexer.ts"&gt;Lexing&lt;/a&gt; (Tokenization)&lt;/li&gt;
&lt;li&gt;
&lt;a href="//src/parser.ts"&gt;Parsing&lt;/a&gt; (AST Creation)&lt;/li&gt;
&lt;li&gt;
&lt;a href="//src/translator.ts"&gt;Translation&lt;/a&gt; (Code generation)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  flowchart TD;
      A[Code] --&amp;gt; B[Convert code to tokens];
      B --&amp;gt; C[Parse tokens to create Expressions];
      C --&amp;gt; D[Combine expressions to create statements];
      D --&amp;gt; E[Combine statements to create AST];
      E --&amp;gt; F[Translate AST to JS code]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="//src/engine.ts"&gt;Engine&lt;/a&gt; class abstracts the above steps and provides a convenient way to use the json templates to evaluate the inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;npm install rudder-json-template-engine&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;engine&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;JsonTemplateEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`'Hello ' + .name`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&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;World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;Template is a set of statements and result the last statement is the output of the template.&lt;/p&gt;

&lt;h3&gt;
  
  
  Variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Input and Bindings
&lt;/h3&gt;

&lt;p&gt;Input refers to the JSON document we would like to process using a template. Bindings refer to additional data or functions we would provide to process the data efficiently.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Template: &lt;code&gt;"Hello " + (.name ?? $.defaultName)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Evaluation: &lt;code&gt;engine.evaluate({name: 'World'}, {defaultName: 'World'});&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{name: 'World'}&lt;/code&gt; is input.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;^.name&lt;/code&gt; refers to "name" property of the input. We can also use &lt;code&gt;.name&lt;/code&gt; to refer the same. &lt;code&gt;^&lt;/code&gt; always refers to the root of the input and &lt;code&gt;.&lt;/code&gt; refers to current context. Refer the &lt;a href="//../test/scenarios/selectors/context_variables.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;{defaultName: 'World'}&lt;/code&gt; is bindings.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$.defaultName&lt;/code&gt; refers to "defaultName" property of the bindings. Refer the &lt;a href="//../test/scenarios/bindings/template.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Arrays
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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;// [2, 3]&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arr&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// [1, 2]&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&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="c1"&gt;// [3, 4]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refer the &lt;a href="//../test/scenarios/arrays/template.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Objects
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;some key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;// { "a": 1, "b": 2, "c": 3, "some key": 4 }&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;b&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="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&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="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&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;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="c1"&gt;// { "a": 1, "b": 2}&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&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;a&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;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="c1"&gt;// { "c": 3, "some key": 4}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refer the &lt;a href="//../test/scenarios/objects/template.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Functions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Normal functions
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="nx"&gt;arg1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;arg2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of the last statement of function will be returned as result of the function. We can also use rest params (&lt;code&gt;...args&lt;/code&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Lambda/Short functions
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&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;lambda&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function gets converted to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&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="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;args&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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lambda functions are short to express the intention and it is convenient sometimes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Async functions
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg2&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;doSomethingAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;doSomethingSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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;&lt;strong&gt;Note:&lt;/strong&gt; When we want to use async functions then we need to create template engine using &lt;code&gt;JsonTemplateEngine.create&lt;/code&gt;. If you create a template this way then it will be created as an async function so we can &lt;code&gt;await&lt;/code&gt; anywhere in the template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Paths
&lt;/h3&gt;

&lt;p&gt;Paths are used to access properties in &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;bindings&lt;/code&gt; and &lt;code&gt;variables&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Simple Paths
&lt;/h4&gt;

&lt;p&gt;Simple paths support limited path features and get translated as direct property access statements in the generate javascript code.&lt;br&gt;
&lt;code&gt;a.b.c&lt;/code&gt; gets translated to &lt;code&gt;a?.b?.c&lt;/code&gt; so they are very fast compared to Rich paths. Simple paths are ideal when we know the object structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supported features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple Selectors&lt;/li&gt;
&lt;li&gt;
Single Index Filters
Refer the &lt;a href="//../test/scenarios/paths/simple_path.jt"&gt;example&lt;/a&gt; for more clarity.
#### Rich Paths
Rich paths gets converted complex code to support different variations in the data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we use rich path for expression: &lt;code&gt;a.b.c&lt;/code&gt; then it automatically following variations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[{"a": { "b": [{"c": 2}]}}]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{"a": { "b": [{"c": 2}]}}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{"a": [{ "b": [{"c": 2}]}]}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Automatically handles selection from nested objects and arrays.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Simple selectors
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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 key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Refer the &lt;a href="//../test/scenarios/selectors/template.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;
&lt;h4&gt;
  
  
  Wildcard selectors
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="c1"&gt;// selects c from any direct property of a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Refer the &lt;a href="//../test/scenarios/selectors/wild_cards.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;
&lt;h4&gt;
  
  
  Descendent selectors
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// selects c from any child property of a&lt;/span&gt;
&lt;span class="c1"&gt;// a.b.c, a.b1.b2.c or a.b1.b2.b3.c&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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 key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Refer the &lt;a href="//../test/scenarios/selectors/template.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;
&lt;h4&gt;
  
  
  Single Index or Property Filters
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&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="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// selects last element from array&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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 key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Refer the &lt;a href="//../test/scenarios/filters/array_filters.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;
&lt;h4&gt;
  
  
  Multi Indexes or Properties Filters
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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 key1&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;some key2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Refer the &lt;a href="//../test/scenarios/filters/array_filters.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;
&lt;h4&gt;
  
  
  Range filters
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[:&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="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Object Property Filters
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&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;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]};&lt;/span&gt;  &lt;span class="c1"&gt;// selects a and b&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&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;a&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;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]};&lt;/span&gt; &lt;span class="c1"&gt;// selects all properties except a and b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Refer the &lt;a href="//../test/scenarios/filters/object_indexes.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;
&lt;h4&gt;
  
  
  Conditional or Object Filters
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;{.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Refer the &lt;a href="//../test/scenarios/filters/object_filters.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;
&lt;h4&gt;
  
  
  Block expressions
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.({&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&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="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.([.&lt;/span&gt;&lt;span class="nx"&gt;a&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;b&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Refer the &lt;a href="//../test/scenarios/paths/block.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;
&lt;h4&gt;
  
  
  Context Variables
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;order&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&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="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="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;orderNum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Use context variables: &lt;code&gt;@order&lt;/code&gt; and &lt;code&gt;#idx&lt;/code&gt;, we can combine properties of orders and products together. Refer the &lt;a href="//../test/scenarios/context_variables/template.jt"&gt;example&lt;/a&gt; for more clarity.&lt;/p&gt;
&lt;h4&gt;
  
  
  Path Options
&lt;/h4&gt;

&lt;p&gt;We can mention defaultPathType while creating engine instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// For using simple path as default path type&lt;/span&gt;
&lt;span class="c1"&gt;// a.b.c will be treated as simple path&lt;/span&gt;
&lt;span class="nx"&gt;JsonTemplateEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`a.b.c`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;defaultPathType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PathType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIMPLE&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// For using rich path as default path type&lt;/span&gt;
&lt;span class="c1"&gt;// a.b.c will be treated as rich path&lt;/span&gt;
&lt;span class="nx"&gt;JsonTemplateEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`a.b.c`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;defaultPathType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PathType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RICH&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can override the default path option using tags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use ~s to treat a.b.c as simple path&lt;/span&gt;
&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; 
&lt;span class="c1"&gt;// Use ~r to treat a.b.c as rich path&lt;/span&gt;
&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Rich paths are slower compare to the simple paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compile time expressions
&lt;/h3&gt;

&lt;p&gt;Compile time expressions are evaluated during compilation phase using compileTimeBindings option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// {{$.a.b.c}} gets translated to 1 and&lt;/span&gt;
&lt;span class="c1"&gt;// final translated code will be "let a = 1;"&lt;/span&gt;
&lt;span class="nx"&gt;JsonTemplateEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`let a = {{$.a.b.c}};`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;compileTimeBindings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&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="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;We can use compile time expressions to generate a template and then recompile it as expression. Refer the &lt;a href="//../test/scenarios/compile_time_expressions/two_level_path_processing.jt"&gt;example&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We have created a simple language to process JSON data efficiently and made it &lt;a href="https://github.com/rudderlabs/rudder-json-template-engine" rel="noopener noreferrer"&gt;open-source&lt;/a&gt;. It was fun building a language, and I hope you have learned something new today.&lt;/p&gt;

</description>
      <category>json</category>
      <category>javascript</category>
      <category>template</category>
      <category>language</category>
    </item>
    <item>
      <title>NodeJS Workflow Engine for JSON data transformations</title>
      <dc:creator>Dilip Kola</dc:creator>
      <pubDate>Wed, 08 Mar 2023 18:16:30 +0000</pubDate>
      <link>https://forem.com/koladilip/nodejs-workflow-engine-for-json-data-transformations-1m7f</link>
      <guid>https://forem.com/koladilip/nodejs-workflow-engine-for-json-data-transformations-1m7f</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In transformer service, we are doing data transformation from customer events to destination events, and we want organize the transformation process into logically separated steps for better understanding and maintainability so we have created a workflow engine to address the following requirements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation

&lt;ul&gt;
&lt;li&gt;Event validation&lt;/li&gt;
&lt;li&gt;Destination config validation&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Source to destination data mapping&lt;/li&gt;

&lt;li&gt;Enriching data with destination API calls&lt;/li&gt;

&lt;li&gt;Handling different types of events

&lt;ul&gt;
&lt;li&gt;Track, Identify, Page, etc.&lt;/li&gt;
&lt;li&gt;Custom categories:

&lt;ul&gt;
&lt;li&gt;Product Viewed&lt;/li&gt;
&lt;li&gt;Product Purchased&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;Multiplexing&lt;/li&gt;

&lt;li&gt;Batching&lt;/li&gt;

&lt;li&gt;Response building.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Currently, most steps are implemented using Javascript code, which provides the most flexibility. Still, it is getting difficult to maintain, understand, debug, test, and develop in a standardized way. To bring standardization, we are building a workflow engine that is config driven to provide improved readability, testability, reusability, and speed of development.&lt;/p&gt;

&lt;p&gt;Since we want to express the transformation of the logic using easy to read and write template based language. We support following template languages: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jsonata-js/jsonata" rel="noopener noreferrer"&gt;JSONata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rudderlabs/rudder-json-template-engine" rel="noopener noreferrer"&gt;JsonTemplate&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Easily extendable to &lt;a href="https://github.com/rudderlabs/rudder-workflow-engine/tree/main/src/steps/base/simple/executors/template" rel="noopener noreferrer"&gt;more template languages&lt;/a&gt;.
&lt;strong&gt;Workflow Example using Jsonata:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;templateType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Jsonata&lt;/span&gt;
&lt;span class="na"&gt;steps&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;unsupported&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$not(op in ["+", "-", "*", "/"])&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;$doThrow("unsupported operation")&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;add&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;Do addition&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;op = "+"&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;( a + b )&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;subtract&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;Do subtraction&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;op = "-"&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;( a - b )&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;multiply&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;Do multiplication&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;op = "*"&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;( a * b )&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;divide&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;Do division&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;op = "/"&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;( &lt;/span&gt;
        &lt;span class="s"&gt;$assert( b != 0, "division by zero is not allowed");&lt;/span&gt;
        &lt;span class="s"&gt;a / b &lt;/span&gt;
      &lt;span class="s"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm install rudder-workflow-engine&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workflowEngine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;WorkflowEngineFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFromFilePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workflow.yaml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;workflowEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Config Driven
&lt;/h3&gt;

&lt;p&gt;Users should be able to express the destination transformation logic as a series of steps in a YAML file as a workflow. Steps can be written as template base languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bindings
&lt;/h3&gt;

&lt;p&gt;Supports importing of external functions and data using bindings.&lt;/p&gt;

&lt;h4&gt;
  
  
  Workflow Bindings
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Bindings are similar to imports, which allow importing of externally defined functions and data to the workflow.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Types&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type 1: Import a specific field from a file.
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EventType&lt;/span&gt; 
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EvenType&lt;/strong&gt; is defined in &lt;strong&gt;./config&lt;/strong&gt; file then it will be imported as &lt;strong&gt;$EventType&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Type 2: Import everything from a file as something.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MappingData&lt;/span&gt; 
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./mapping&lt;/span&gt;
  &lt;span class="na"&gt;exportAll&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;ul&gt;
&lt;li&gt;Everything from &lt;strong&gt;./mapping&lt;/strong&gt; file will be imported to the variable &lt;strong&gt;MappingData&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If &lt;strong&gt;something1&lt;/strong&gt; and &lt;strong&gt;something2&lt;/strong&gt; are defined in &lt;strong&gt;./mapping&lt;/strong&gt; then we need to access them using &lt;strong&gt;$MappingData.something1&lt;/strong&gt; and &lt;strong&gt;$MappingData.something2&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Type 3: Import everything as it is defined in the file
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./utils&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;Everything from &lt;strong&gt;./utils&lt;/strong&gt; file will be imported with the same names.&lt;/li&gt;
&lt;li&gt;If &lt;strong&gt;something1&lt;/strong&gt; and &lt;strong&gt;something2&lt;/strong&gt; are defined in &lt;strong&gt;./utils&lt;/strong&gt; then we need to access them using &lt;strong&gt;$something1&lt;/strong&gt; and &lt;strong&gt;$something2&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Full example:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;bindings&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;EventType&lt;/span&gt; 
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./config&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;MappingData&lt;/span&gt; 
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./mapping&lt;/span&gt;
      &lt;span class="na"&gt;exportAll&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./utils&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;These are user-specified bindings while defining the workflow.
#### Platform bindings&lt;/li&gt;
&lt;li&gt;The platform provides these bindings, which can be used directly without defining them in the &lt;strong&gt;bindings&lt;/strong&gt; block.&lt;/li&gt;
&lt;li&gt;We will soon release detailed documentation on these bindings.
#### Execution bindings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$outputs:&lt;/strong&gt; Provides access to the outputs of the previous steps executed before the current step.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;steps&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;step1&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;{&lt;/span&gt;
          &lt;span class="s"&gt;"a": something&lt;/span&gt;
        &lt;span class="s"&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;step2&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"b": $doSomething($outputs.step1.a)&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step2&lt;/strong&gt; uses the output of &lt;strong&gt;step1.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Workflow Engine automatically bindings step outputs to the &lt;strong&gt;$outputs&lt;/strong&gt; variable.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;$setContext:&lt;/strong&gt; It is a function to store any data in $context and use it later. &lt;strong&gt;$outputs&lt;/strong&gt; are read-only variables for users to refer to the previous step outputs, so we can’t use them to pass a modifiable result. So if we want to update the same variable in multiple steps, then &lt;strong&gt;$setContext&lt;/strong&gt; should be used.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&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;setAForCase1&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$isCase1(message)&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;$setContext("a", something1)&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;setAForCase2&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$isCase2(message)&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;$setContext("a", something2)&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;updateA&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;$setContext("a", $updateA($context.a))&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;useA&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;$doSomething($context.a)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;In this example, we update the variable repeatedly in several steps, so it is impossible to use &lt;strong&gt;$outputs.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A practical scenario for this feature is: that we want to populate an object differently based on some conditions and later use it.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;$context:&lt;/strong&gt; To access variables set using &lt;strong&gt;$setContext&lt;/strong&gt; function. Please refer to the above example for clarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Steps are the main execution blocks of the workflow.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Steps must contain a &lt;strong&gt;name&lt;/strong&gt; to track outputs.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Steps can contain an optional &lt;strong&gt;description&lt;/strong&gt; field to describe the details.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;The step can contain an optional &lt;strong&gt;condition&lt;/strong&gt; field to execute only if the condition is satisfied.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;The step can contain an optional &lt;strong&gt;inputTemplate&lt;/strong&gt; field to customize the input, which will be passed while executing the step.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;There are two different types of steps supported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SimpleStep&lt;/li&gt;
&lt;li&gt;WorkflowStep&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conditions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A step in a workflow can mention an optional condition so that it gets executed only when the condition is satisfied.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Condition is also a &lt;a href="https://docs.jsonata.org/overview.html" rel="noopener noreferrer"&gt;Jsonata&lt;/a&gt; code.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&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;commonValidation&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;( common validations for events )&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;ValidateInputOfTrackEvent&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;message.type = EventType.Track&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;( some validations specific to track events)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  InputTemplate**
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;By default, all steps receive the same input as the workflow input, but when we want to modify the input before executing the step, we can use this feature.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;steps&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;step1&lt;/span&gt;
      &lt;span class="s"&gt;(some logic ...)&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;step2&lt;/span&gt;
      &lt;span class="na"&gt;inputTemplate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;(customize the input)&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;step3&lt;/span&gt;
      &lt;span class="s"&gt;(some logic ...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the above example: step1 and step3 will be executed with the workflow’s input, but the step2 receives custom input as defined in the &lt;strong&gt;inputTemplate&lt;/strong&gt;
### ContextTemplate

&lt;ul&gt;
&lt;li&gt;By default, all steps receive the current context, but we can use this feature when we want to modify the context before executing the step. This is useful when using external workflows, workflow steps, or template paths.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;steps&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;step1&lt;/span&gt;
      &lt;span class="s"&gt;(some logic to prepareContext)&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;step2&lt;/span&gt;
      &lt;span class="na"&gt;contextTemplate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;(customize the context for step2)&lt;/span&gt;
      &lt;span class="s"&gt;(some logic ...)&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;step3&lt;/span&gt;
      &lt;span class="s"&gt;(some logic ...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the above example: step 3 will execute with the context prepared in step 1, but step 2 receives custom context as defined in the &lt;strong&gt;contextTemplate.&lt;/strong&gt;
### LoopOverInput

&lt;ul&gt;
&lt;li&gt;We can use this feature when the input is an array, and we want to execute the step logic for each element independently.&lt;/li&gt;
&lt;li&gt;This is mainly used for batch processing, and we report failed and successful executions without failing the step if an error occurs while processing a particular step.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;executeForEach&lt;/span&gt;
  &lt;span class="na"&gt;loopOverInput&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;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;( do something )&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If the input for the step is [e1, e2, e3], then the step will be executed for all elements independently, and imagine that it failed for e1 and succeeded for e2 and e3 then, the overall step output will be the following:&lt;br&gt;
&lt;/p&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;someErrorForE&lt;/span&gt;&lt;span class="mi"&gt;1&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;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;someOutputForE&lt;/span&gt;&lt;span class="mi"&gt;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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;someOutputForE&lt;/span&gt;&lt;span class="mi"&gt;3&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;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  OnComplete
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;When the step is completed, the next step will be executed, but if we want to exit the workflow with the output of a particular step, then we can use this.&lt;/li&gt;
&lt;li&gt;This feature should be used only in a conditional step.&lt;/li&gt;
&lt;li&gt;Example 1: Avoid reprocessing, so return without modifying the input message.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;steps&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;checkIfProcessed&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;message.processed = &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;message&lt;/span&gt;
        &lt;span class="na"&gt;**onComplete&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;return**&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;processMessage&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the above example, we don’t want to reprocess messages, so we need to return them immediately if they are already processed.

&lt;ul&gt;
&lt;li&gt;Example 2: Return early after processing the input message.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;steps&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;step1&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;(doSomeProcessing)&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="err"&gt;*&lt;/span&gt;&lt;span class="nv"&gt;*step2&lt;/span&gt;&lt;span class="err"&gt;**&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;someCondition&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;(doSomeProcessing)&lt;/span&gt;
      &lt;span class="na"&gt;onComplete&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;return&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;step3&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;(doSomeProcessing)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In this example, we want to &lt;strong&gt;return early&lt;/strong&gt; after successfully processing the message in &lt;strong&gt;step2&lt;/strong&gt; since this step is conditional, and if the condition is not satisfied, then &lt;strong&gt;step3&lt;/strong&gt; will be executed.
### OnError

&lt;ul&gt;
&lt;li&gt;By default, if any step fails, then the entire workflow fails but if the step uses &lt;strong&gt;OnError: continue&lt;/strong&gt; setting, then the workflow will ignore the error and continue with execution.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;steps&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;step1&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;(doSomeProcessing)&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="err"&gt;*&lt;/span&gt;&lt;span class="nv"&gt;*step2&lt;/span&gt;&lt;span class="err"&gt;**&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;(doSomeProcessing)&lt;/span&gt;
      &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;continue&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;step3&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;(doSomeProcessing)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the above example, if any error occurs in either step1 or step3, the workflow will exit immediately, but when step2 fails, the workflow ignores the error and continues to execute step3.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Simple Step
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Simple step is the basic unit of execution in the workflow.&lt;/li&gt;
&lt;li&gt;A simple step can be a &lt;strong&gt;function&lt;/strong&gt; that is defined in the &lt;strong&gt;Bindings&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;bindings&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="err"&gt;*&lt;/span&gt;&lt;span class="nv"&gt;*processTrackEvent&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./transform&lt;/span&gt; &lt;span class="c1"&gt;# actual file name is transform.js**&lt;/span&gt;
  &lt;span class="na"&gt;steps&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;processTrackEvent&lt;/span&gt;
      &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;processTrackEvent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;We can omit &lt;strong&gt;.js&lt;/strong&gt; extension while defining the bindings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;processTrackEvent&lt;/strong&gt; must have the following definition.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bindings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;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="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="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;A simple step can be a JSONata template.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;processTrackEvent&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;(JSONata template to process track events)**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The template also can be imported from the file path.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;processTrackEvent&lt;/span&gt;
&lt;span class="na"&gt;templatePath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./trackTemplate.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;ul&gt;
&lt;li&gt;We can use an &lt;strong&gt;external workflow&lt;/strong&gt; in a simple step.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;steps&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;prepareContext&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$setContext("batchMode", &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&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;transform&lt;/span&gt;
      &lt;span class="na"&gt;**externalWorkflow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./pinterest_tag_single_workflow.yaml**&lt;/span&gt;
      &lt;span class="na"&gt;loopOverInput&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;ul&gt;
&lt;li&gt;We are reusing the single event workflow in the batch events transformation workflow.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;external workflow&lt;/strong&gt; will be executed as a black box, so we can only access the final output of the workflow but not the individual outputs of steps.&lt;/li&gt;
&lt;li&gt;The external workflow is executed with &lt;strong&gt;step input&lt;/strong&gt; and &lt;strong&gt;context&lt;/strong&gt; of the original workflow.&lt;/li&gt;
&lt;li&gt;The context of the parent workflow is passed to the child workflow (&lt;strong&gt;externalWorkflow&lt;/strong&gt;) but not vice-versa. This is helpful to customize the child workflow execution based on where it is used.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;external workflow&lt;/strong&gt; doesn’t have access to the parent workflow &lt;strong&gt;outputs.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Workflow Step
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Series of &lt;strong&gt;simple&lt;/strong&gt; steps.
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&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;category&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;(compute category)&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;ecom&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$outputs.category = "ecom"&lt;/span&gt;
    &lt;span class="na"&gt;steps&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;validateInput&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;Common validation for all ECom pages&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;(assert everything is fine)&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;page&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;(compute page using $outputs.category)&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;processSearchPage&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$outputs.ecom.page = "search"&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;(search page template)&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;processDetailPage&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$outputs.ecom.page = "detail"&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;(detail page template)&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;processCartPage&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$outputs.ecom.page = "cart"&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;(cart page template)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;We can access &lt;strong&gt;outputs&lt;/strong&gt; of previous steps normally like &lt;strong&gt;$outputs.category.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;To access outputs of the child steps of the workflowStep, we need to use &lt;strong&gt;$outputs.workflowStepName.childStepName&lt;/strong&gt;, for example: $outputs.ecom.page.

&lt;ul&gt;
&lt;li&gt;The outputs of the child steps are not available outside the workflow step.&lt;/li&gt;
&lt;li&gt;The last successfully executed child step’s output will become the output of the workflow step, and we can only access that outside the workflow step as &lt;strong&gt;$output&lt;/strong&gt;.&lt;strong&gt;workflowStepName,&lt;/strong&gt; for example, $output.ecom.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Currently, we don’t support nested workflow steps.&lt;/li&gt;

&lt;li&gt;Workflow Step can be imported from a file.
&lt;/li&gt;

&lt;/ul&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&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;processECommerace&lt;/span&gt;
    &lt;span class="na"&gt;workflowStepPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./ecomWorkflow.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;Supports additional &lt;strong&gt;Bindings&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;bindings&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;commonBinding&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./bindings&lt;/span&gt;
&lt;span class="na"&gt;steps&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;processECommerace&lt;/span&gt;
    &lt;span class="na"&gt;bindings&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;stepBinding&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./workflow_step_bindings&lt;/span&gt;
    &lt;span class="na"&gt;steps&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;validateInput&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;Common validation for all ECom pages&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;(assert with $commonBinding)&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;page&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;(compute page using $workflowBinding)&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;processSearchPage&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$outputs.ecom.page = "search"&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;(search page template)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;In the above example: &lt;strong&gt;processECommerace&lt;/strong&gt; step is the workflow step and importing additional bindings. Both workflow’s (&lt;strong&gt;commonBinding&lt;/strong&gt;) and step’s (&lt;strong&gt;stepBinding&lt;/strong&gt;) bindings are available to the workflow step.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Test Scenarios using Jest
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm run jest:scenarios --  --scenarios=&amp;lt;comma separate scenarios&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;npm run jest:scenarios --  --scenarios=basic_workflow,to_array&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Manually Test Scenario
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm run test:scenario -- -s &amp;lt;scenario_folder&amp;gt;  -i &amp;lt;test_case_index_from_data.json&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;npm run test:scenario -- -s outputs -i 1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Note: It just run the test case and produces results but won't run any validations of the results.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We have built this &lt;a href="https://github.com/rudderlabs/rudder-workflow-engine" rel="noopener noreferrer"&gt;workflow engine&lt;/a&gt; to solve our problems, and we believe that the framework is general enough for other use cases as well, so please feel free to give it a shot.&lt;/p&gt;

</description>
      <category>node</category>
      <category>workflow</category>
      <category>json</category>
      <category>datatransformation</category>
    </item>
  </channel>
</rss>
